Exoplanet Classification Problem

For this project, we decided to perform classification on our data set. Classification is a way to predict a label, in our case categorical, based on the characteristics of the data. The

The objective of our project is to use machine learning classification models to determine if a given observation of a star should be classified as an exoplanet (a planet outside our solar system). The original data set comes from the Kepler Space Observatory, and details 9,564 observations of potential exoplanets, along with 50 descriptive features ranging from identifiers to specific measurements. The column “koi_disposition” labelled each of the potential exoplanets as either “CONFIRMED,” “FALSE POSITIVE,” or “CANDIDATE”. Those labeled “CANDIDATE” have not yet been determined to be exoplanets or not; the goal of our analysis will be to label them as “CONFIRMED” or “FALSE POSITIVE.” NOTE: In this context, “false positive” does not indicate a false positive output from our model. Instead, it refers to the original observation, which was considered a candidate, to have been falsely identified as a candidate.

To solve this problem, we will carry out the following process:

  1. Load, preprocess, and clean up data
  2. Data visualization and exploratory data analysis
  3. Creation and testing of candidate models:
    • 3.1) Models using non-scaled data:
      • 3.1.1) Decision Tree, Random Forest, and Support Vector Machine
      • 3.1.2) XGBoost
      • 3.1.3) Logistic Regression
    • 3.2) Models using scaled data:
      • 3.2.1) Neural Network: Original Variables
      • 3.2.2) Neural Network: PCA w/13 Variables
      • 3.2.3) Neural Network: PCA w/20 Variables
      • 3.2.4) K-Nearest Neighbours
      • 3.2.5) K-Means Clustering
  4. Select best model
  5. Use best model to make predictions

1. Data Loading, Pre-Processing and Clean Up

Load Libraries:

library(Matrix)    #extra Matrix functionality
library(rpart)      #Decision trees
library(rpart.plot)
library(randomForest)#random forest
randomForest 4.6-14
Type rfNews() to see new features/changes/bug fixes.
library(class)      #KNN
library(e1071)      #misc. stats functionary
library(xgboost)    #XGBoost Algorithm
Warning: package ‘xgboost’ was built under R version 4.1.2
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
library(FNN)        #KNN

Attaching package: ‘FNN’

The following objects are masked from ‘package:class’:

    knn, knn.cv
library(factoextra) #PCA and clustering
Warning: package ‘factoextra’ was built under R version 4.1.2
Loading required package: ggplot2

Attaching package: ‘ggplot2’

The following object is masked from ‘package:randomForest’:

    margin

Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
library(ggplot2)    #extra plotting functionality
library(corrplot)   #extra plotting functionality
corrplot 0.90 loaded
#visualization packages
library(dplyr)

Attaching package: ‘dplyr’

The following object is masked from ‘package:xgboost’:

    slice

The following object is masked from ‘package:randomForest’:

    combine

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(reshape2)
library(tidyverse)
Warning: package ‘tidyverse’ was built under R version 4.1.2
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
-- Attaching packages -------------------------------------------------- tidyverse 1.3.1 --
v tibble  3.1.4     v purrr   0.3.4
v tidyr   1.1.4     v stringr 1.4.0
v readr   2.1.0     v forcats 0.5.1
Warning: package ‘readr’ was built under R version 4.1.2
Warning: package ‘forcats’ was built under R version 4.1.2
-- Conflicts ----------------------------------------------------- tidyverse_conflicts() --
x dplyr::combine()  masks randomForest::combine()
x tidyr::expand()   masks Matrix::expand()
x dplyr::filter()   masks stats::filter()
x dplyr::lag()      masks stats::lag()
x ggplot2::margin() masks randomForest::margin()
x tidyr::pack()     masks Matrix::pack()
x dplyr::slice()    masks xgboost::slice()
x tidyr::unpack()   masks Matrix::unpack()
library(DiagrammeR) #plotting for XGBoost
Warning: package ‘DiagrammeR’ was built under R version 4.1.2
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
library(formulaic) #automated formula creation
Warning: package ‘formulaic’ was built under R version 4.1.2
library(neuralnet) #Neural Networks
Warning: package ‘neuralnet’ was built under R version 4.1.2

Attaching package: ‘neuralnet’

The following object is masked from ‘package:dplyr’:

    compute

The first step was to load the data, and convert our output variable, “koi_disposition”, to a factor

#read data
originalKepplerData = read.csv("cumulative.csv") #read data
factored_keppler_data = originalKepplerData
factored_keppler_data$koi_disposition = factor(originalKepplerData$koi_disposition) #factor character data
factored_keppler_data$koi_pdisposition = factor(originalKepplerData$koi_pdisposition)
head(factored_keppler_data)

Our next step was to conduct forms of dimensionality reduction on our 49 possible input variables. Dimensionality reduction is important for our large data set to reduce the computational requirements of models and reduce possible overfitting or bias from un-important features. First, we removed several entirely empty columns (with values of 0 or NA), unique row identifiers (observation numbers), as well as several which could only be filled once the outcome of the observation was already known. For example, “kepler_name” is the name given to a confirmed exoplanet, so it was removed from the data set. There were no unique outlying data points that had to be explored further.

#remove all-NA columns:
remove_KOI_tech_factored = subset(factored_keppler_data, select = -c(koi_teq_err1)) #remove 
remove_KOI_tech_factored = subset(remove_KOI_tech_factored, select = -c(koi_teq_err2))
remove_NAs = na.exclude(remove_KOI_tech_factored) #remove any rows with NAs remaining

#Remove unique identifiers and rows that can only be known after the status of a planet is known. They therefore are not useful inputs to determine the status of a candidate.
identifiers_removed = subset(
  remove_NAs,
  select = -c(
    rowid,
    kepid,
    kepoi_name,
    kepler_name,
    koi_pdisposition,
    koi_score
  )
)

#Also needs to remove flags, these can only be known after status is confirmed
identifiers_removed = subset(
  identifiers_removed,
  select = -c(
    koi_fpflag_ss,
    koi_fpflag_ec,
    koi_fpflag_co,
    koi_fpflag_nt,
    koi_tce_plnt_num,
    koi_tce_delivname
  )
)

#Now, we will separate in labeled and unlabeled data. The labeled data will be used to train our model; once the best model is selected, we will use it to predict the labels of the unlabeled dataset
candidates_final = identifiers_removed[identifiers_removed$koi_disposition ==
                                         "CANDIDATE",] #separate out just the candidates
labeled_final = identifiers_removed[identifiers_removed$koi_disposition !=
                                      "CANDIDATE",]
labeled_final = droplevels(labeled_final)       
candidates_final = droplevels(candidates_final)
head(labeled_final)
head(candidates_final)

We are left with two datasets:

2) Data Visualization and Exploratory Data Analysis

Select the variables to be used:

#All variables have errors - for exploratory data analysis we will only be looking at the variables themselves, not their errors
variablesonly = labeled_final %>%
  select(koi_period, koi_time0bk, koi_impact, koi_duration, koi_depth, koi_prad, koi_teq, koi_insol, koi_model_snr, koi_steff, koi_slogg, koi_srad, ra, dec, koi_kepmag)

Create a correlation heat map with only the variables (excluding the errors).

cormat <- round(cor(variablesonly),2)
head(cormat)
             koi_period koi_time0bk koi_impact koi_duration koi_depth koi_prad koi_teq
koi_period         1.00        0.60      -0.03         0.33     -0.05    -0.01   -0.35
koi_time0bk        0.60        1.00       0.02         0.20     -0.05    -0.01   -0.28
koi_impact        -0.03        0.02       1.00         0.06      0.02     0.54    0.05
koi_duration       0.33        0.20       0.06         1.00      0.09     0.02   -0.19
koi_depth         -0.05       -0.05       0.02         0.09      1.00     0.08    0.06
koi_prad          -0.01       -0.01       0.54         0.02      0.08     1.00    0.12
             koi_insol koi_model_snr koi_steff koi_slogg koi_srad    ra   dec koi_kepmag
koi_period       -0.02         -0.04      0.01     -0.04     0.01 -0.05  0.02      -0.02
koi_time0bk      -0.02         -0.04     -0.01      0.02    -0.01 -0.03  0.00       0.03
koi_impact       -0.01          0.03      0.08     -0.03     0.00  0.07 -0.03       0.02
koi_duration     -0.02          0.10      0.10     -0.13     0.02  0.04 -0.03      -0.10
koi_depth        -0.01          0.60      0.15     -0.03    -0.02  0.02 -0.01       0.00
koi_prad          0.05          0.05     -0.01     -0.17     0.19  0.03  0.00      -0.01
melted_cormat <- melt(cormat)
head(melted_cormat)

ggplot(data = melted_cormat, aes(x=Var1, y=Var2, fill=value)) + 
  geom_tile()

Defining significant correlations as those greater than .4, there are a few; this will need to be dealt with in Logistic Regression, but should not affect other models.

Plots by FALSE POSITIVE and CONFIRMED

We will now observe the distribution of several variables across false positive and confirmed exoplanets. This will help determine where there is a clear pattern in certain variables.

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = koi_period, y = koi_time0bk)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

For koi_period and koi_time0bk, the distributions of the variables seem similars, with some outliers. That being said, FALSE POSITIVEs can have significantly higher koi_periods than CONFIRMEDs

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = koi_impact, y = koi_duration)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

The plot of koi_impact vs. koi_duration shows clear differences between their distributions - CONFIRMEDs have a significantly small range for both variables, although those candidates that fall inside that range may be difficult to classfiy.

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = koi_depth, y = koi_prad)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

koi_depth shows a clear different: high koi_depths almost always indicate FALSE POSITIVEs

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = koi_teq, y = koi_insol)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

koi_teq has a significantly smaller range for CONFIRMED data points, and KOI_insol stays closer to zero. If we look at

summary(labeled_final$koi_insol)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
       0       36      219     8148     1332 10947555 
summary(labeled_final$koi_teq)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
     92     626     981    1192    1540   14667 

This confirms that there are very large values for FALSE POSITIVES that are significantly outside the normal data range. It will be easy to classify these as FALSE POSITIVEs. We can explore these graphs with a smaller range, to exclude the outliers:

ggplot(data = labeled_final[labeled_final$koi_insol<250000,]) +
  geom_point(mapping = aes(x = koi_teq, y = koi_insol)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

This shows that for values of koi_insol < 25000 and koi_teq<3000, it becomes difficult to identify whether a given data point is FALSE POSITIVE or CONFIRMED.

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = koi_model_snr, y = koi_steff)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

Once again, FALSE POSITIVES show significantly more variability on both axes than CONFIRMEDs

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = koi_slogg, y = koi_srad)) + 
  facet_wrap(~ koi_disposition, nrow = 2)

FALSE POSITIVEs can have significantly lower koi_sloggs and higher koi_srads than CONFIRMEDs

ggplot(data = labeled_final) +
  geom_point(mapping = aes(x = ra, y = dec)) +
  facet_wrap( ~ koi_disposition, nrow = 2)

Lastly, for ra and dec, there is no clear different in distribution for CONFIRMED or FALSE POSITIVE.

Overall, an easy way to determine FALSE POSITIVEs tends to be to look for data points outside a given range. Inside that range, the determination of whether an observation is a exoplanet or not becomes more difficult.

3. Creation and testing of candidate models

Tto classify the data and determine which observation are, in fact, planets, our group first had to determine which model most accurately predicted our labeled data set. We first examined models that did not require scaling, these included: Decision Tree, Random Forest, XGBoost, Logistic Regression, and Support Vector Machines (SVM). Next, we examined models that required scaling, these included: KNN, Neural Network (with and without Principal Component Analysis), and K-means clustering. To choose the best model for our data set, our group decided to look at which model produced the lowest misclassification rate. However, it should be noted that in real-life application a number of other metrics should be considered as well. These include, but are not limited to, precision analysis (PR curves), recall, ROC-AUC, and F1 Scores.

When running the various models, our group incorporated K-fold cross-validation. The benefit of this approach is that it allows the model to become more generalized, helping with over-fitting concerns. In addition, we take the average result of five misclassification rates for each model, significantly lowering the chance that a given misclassification rate is produced only by chance due to a specific testing/training set. This allows us to be more confident that the model with the lowest misclassification rate truly is the best. Due to computational limits, we decided to use a K value of 5 as we believed that would be adequate for our purposes. Finally, when deciding the proportion of training and testing set, we decided to follow class standards with 80% training set and 20% testing set. Our specific data set is fairly large with ~6,031 rows; we believe having ~4800 rows to train and ~1,200 rows to test is large enough for each purpose.

3.1) Models using non-scaled data:

3.1.1) Decision Tree, Random Forest, and SVM

The goal of a decision tree is to make splitting decisions on the data, in an effort to minimize the least squares, thus creating a tree-like structure. These models are useful as a starting point because they are easy to interpret as the plot can display which variables have the highest importance in the tree. Normalization and other data cleaning is also not required for this model. Adding more depth to the tree can reduce the fitting error to the data, but it can lead to overfitting the model. As a result, the complexity parameter, cp, must be tuned to ensure that the optimal depth-to-fit of the model is used. As the cp value decreases, so does the relative error in the model. We automatically prune our decision tree to select the cp where the change in error is less than 0.05; this is our first example of parameter tuning.

Random forest models are more accurate and robust but harder to interpret than a single tree. The model creates many decision trees with different randomized learning and testing sets, then the trees “vote” or “average” their results to determine the resultant random forest model. Though the model is not as interpretable as a single tree and it is more difficult to understand the significance of a single variable, it will result in lower misclassification rate. The number of trees in the forest is a parameter that needs to be tuned in this model. As the number of trees increases, the error decreases exponentially, reaching an asymptote of error.

SVM models are supervised learning models that use the data points to create a line to separate the data. This separation then decides the binary classification for each data point. While this model can be incredibly versatile and robust against outliers and inaccurate data, it may not be as accurate if there is much overlap between the data.

Code for decision tree, random forest, and SVM:

decTree_Error = rep(0, 5)
RF_error = rep(0, 5)
SVM_error = rep(0, 5)

for (fold in 1:5) {
  #set up k-crossfold validation
  set.seed(fold)
  
  
  #split data into testing and training sets
  num_samples = dim(labeled_final)[1]
  sampling.rate = 0.8
  training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
  trainingSet = subset(labeled_final[training,])
  testing = setdiff(1:num_samples, training)
  testingSet = subset(labeled_final[testing, ])
  
  
  #Decision tree
  decTreeModel = rpart(koi_disposition ~ ., data = trainingSet)
  
  #Automatically select the stopping point where cp no longer improves error by 0.05
  errors = decTreeModel$cptable[, 3]
  decTreeChangeError = c(0, 0, 0, 0, 0, 0, 0, 0, 0)
  for (i in 1:8) {
    decTreeChangeError[i] = errors[i + 1] - errors[i]
  }
  decTreeChangeError
  for (i in 1:9) {
    if (abs(decTreeChangeError[i]) < 0.05) {
      stopIndex = i
      break
    }
  }
  cps = decTreeModel$cptable[, 1]
  cpStop = cps[stopIndex]
  
 
  prunedDecTreeModel = rpart::prune(decTreeModel, cp = cpStop) #Prune decision tree
  decTreePredictions = predict(prunedDecTreeModel, testingSet, type = "class") #make predictions
  #Determine decision tree error
  sizeTestSet = dim(testingSet)[1]
  decTreeModel_error = sum(decTreePredictions != testingSet$koi_disposition)
  misclassificationRateDecTree = decTreeModel_error / sizeTestSet
  decTree_Error[fold] = misclassificationRateDecTree

  #Random Forest
  RandForestModel = randomForest(koi_disposition ~ ., data = trainingSet)
  predictedLabels = predict(RandForestModel, testingSet)
  
  #Determine Random Forest Error
  sizeTestSet = dim(testingSet)[1]
  error = sum(predictedLabels != testingSet$koi_disposition)
  misclassification_rateRandForest = error / sizeTestSet
  RF_error[fold] = misclassification_rateRandForest
  
  #SVM Model
  svmModel = svm(koi_disposition ~ ., data = trainingSet, kernel = "linear")
  predictedlabelsSVM = predict(svmModel, testingSet)
  #Determine SVM error
  errorSVM = sum(predictedlabelsSVM != testingSet$koi_disposition)
  misclassification_rateSVM = errorSVM / sizeTestSet
  SVM_error[fold] = misclassification_rateSVM
}
#Take average of errors from each fold to determine average error for each model
AvgErrorDT = mean(decTree_Error)
AvgErrorRF = mean(RF_error)
AvgErrorSVM = mean(SVM_error)
paste("DT Error: ", round(100*AvgErrorDT,2),"%",sep = "")
[1] "DT Error: 11.58%"
paste("RF Error: ", round(100*AvgErrorRF,2),"%",sep = "")
[1] "RF Error: 6.74%"
paste("SVM Error: ", round(100*AvgErrorSVM,2),"%",sep = "")
[1] "SVM Error: 8.7%"

All three models give low misclassification rates <12%, but Random Forest gives the best misclassification rate at only 6.74%.

3.1.2) XGBoost

XGBoost stands for “extreme gradient boosting” and is an open-source tree learning algorithm similar to random forests that is also widely used in industry. This model seeks to minimize an objective function representing model complexity and loss (error), using a gradient descent algorithm to minimize loss when adding new models. This is known as tree boosting; random forest models differ because they use a tree bagging algorithm, possibly leading to different model accuracies.

XGBoost outputs a probability between 0 and 1, rather than a binary classification. For this reason, it is necessary to determine the “threshold” where a value stops being a FALSE POSITIVE, and start being a CONFIRMED. Questioning the classification threshold which is built into our models can help us develop more accurate models. For example, arbitrarily assuming that the threshold for classifying based on our XGBoost model was exactly 0.5 could have resulted in a higher misclassification rate if we were to consider all possible thresholds. As a result, we conducted threshold analysis on all our models that output probabilities by looping through all classification thresholds at 0.1 increments, plotted them against their respective misclassification rates, and took the minimum as the optimal. The first model that we have done this for is XGBoost.

xgb.set.config(verbosity = 0)
[1] TRUE
threshold = 0.1
XGBoost_error_thresholds = rep(0, 10)
index = 1
while (threshold < 1) {
  #loop that reruns the algorithm with increments of 0.1 in the threshold
  XGB_error = rep(0, 5)
  for (fold in 1:5) {
    #k cross-fold validation
    set.seed(fold)
    
    num_samples = dim(labeled_final)[1]
    
    #create testing and training set
    sampling.rate = 0.8
    training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
    trainingSet = subset(labeled_final[training,])
    testing = setdiff(1:num_samples, training)
    testingSet = subset(labeled_final[testing, ])
    
    #XGBoost model
    xgTrain = data.matrix(trainingSet)
    xgTrain[, 1] = ifelse(xgTrain[, 1] == 2, 1, 0)
   
    xgBoostModel = xgboost(
      data = xgTrain[, 2:36],
      label = xgTrain[, 1],
      max.depth = 6,
      eta = .22,
      nrounds = 100,
      verbose = 0,
      objective = "binary:logistic",
      eval_metric="error"
    )
    xgTest = data.matrix(testingSet)
    xgTest[, 1] = ifelse(xgTest[, 1] == 2, 1, 0)
    
    #make predictions
    BoostPredictions = predict(xgBoostModel, data.matrix(testingSet)[, 2:36])
    BoostPredictionsRounded = ifelse(BoostPredictions > threshold, 1, 0) #convert probabilities to ouputs of 1 or 0 based on whether they are greater than the threshold - this is where parameter tuning occurs.
    BoostError = sum(BoostPredictionsRounded != xgTest[, 1])
    misclassificationRateBoost = BoostError / dim(xgTest)[1]
    XGB_error[fold] = misclassificationRateBoost
  }
  XGBoost_error_thresholds[index] = mean(XGB_error)#average the error from all folds
  index = index + 1
  threshold = threshold + .1
}
xgbPlot = xgb.plot.tree(model = xgBoostModel,
                        trees = 1,
                        render = TRUE)
xgbPlot #plot an example tree


#Determine correct threshold value
XGBoost_error_thresholds#shows the average error at each threshold
 [1] 0.07854184 0.06611433 0.06197183 0.06081193 0.06081193 0.06362883 0.06545153
 [8] 0.06992543 0.07887324 0.62220381
order(XGBoost_error_thresholds) #Lowest error is threshold = .4
 [1]  4  5  3  6  7  2  8  1  9 10
AvgErrorXGB = XGBoost_error_thresholds[(order(XGBoost_error_thresholds)[1])] #chose the threshold with the lowest error as the one we use
paste("XGBoost Error: ", round(AvgErrorXGB*100,2), "%", sep = "")
[1] "XGBoost Error: 6.08%"
#plot the error thresholds
plot(
  x = 1:10 / 10,
  y = XGBoost_error_thresholds,
  main = "Avg Error over different thresholds for XGBoost Model",
  xlab = "Cutoff Threshold",
  ylab = "Misclassification Rate"
)
lines(x = 1:10 / 10, y = XGBoost_error_thresholds)

XGBoost gives an error rate of 6.08%. This is significantly better than any model run so far. The threshold analysis graph above shows that the best misclassification rate for XGBoost is actually at the threshold = .4.

3.1.5) Logistic Regression

Logistic regression models is a supervised classification algorithm that builds a regression model to predict the classification by assigning data entries to binary values, based on the Sigmoid function. When performing logistic regression, it is important to consider the problems that arise from multicollinearity which can cause unstable estimates and inaccuracy. For this reason, we decided to first remove all major multicollinearity from the model.

#Check for multicollinearity
corrFrame = data.frame(cor(labeled_final[, 2:36]))
corrplot(cor(labeled_final[, 2:36])) #many multicollinear variables. Definine collinearity as correlation >.4



GLM_Data = data.frame(labeled_final$koi_disposition)


#all err2s are collinear with err1s. Removing err1s
variable_counter = 2
variable.names = colnames(labeled_final)
for (i in 2:length(labeled_final)) {
  variable.names[i]
  error1 = grepl("_err1", variable.names[i], fixed = TRUE)
  if (error1 == FALSE) {
    GLM_Data[, variable_counter] = labeled_final[, i]
    variable_counter = variable_counter + 1
  } else{
    variable.names[i] = NA
  }
}
variable.names = na.omit(variable.names)
colnames(GLM_Data) = variable.names
GLM_Data

corrFrame2 = data.frame(cor(GLM_Data[, 2:dim(GLM_Data)[2]]))
corrplot(cor(GLM_Data[, 2:dim(GLM_Data)[2]])) #Many correlated variables remain



#KOI_period is collinear with koi_time0b
GLM_Data = subset(GLM_Data, select = -c(koi_time0bk))
#koi_period_err is collinear with koi_time0bk error
GLM_Data = subset(GLM_Data, select = -c(koi_time0bk_err2))
#Koi_period is collinear with koi_period_err2
GLM_Data = subset(GLM_Data, select = -c(koi_period_err2))
#koi_impact is collinear with koi_impact_err2
GLM_Data = subset(GLM_Data, select = -c(koi_impact_err2))
#koi_depth is collinear with koi_model_snr
GLM_Data = subset(GLM_Data, select = -c(koi_model_snr))
#koi impact is collinear with koi_prad and koi_prad_err
GLM_Data = subset(GLM_Data, select = -c(koi_prad, koi_prad_err2))
#koi_teq is collinear with KOI_insol, Koi_insol_err, koi_slogg, koi_srad, and koi_srad_err
GLM_Data = subset(GLM_Data,
                  select = -c(koi_insol, koi_insol_err2, koi_slogg, koi_srad, koi_srad_err2))
#koi_steff is collinear with koi_steff_err2 and koi_slogg_err2
GLM_Data = subset(GLM_Data, select = -c(koi_slogg_err2, koi_steff_err2))
corrplot(cor(GLM_Data[, 2:dim(GLM_Data)[2]]))

#All major multicollinearity has now been removed

Similarly to XGBoost, the cutoff threshold for prediction must be tuned. Our group decided to run multiple tests ranging from 0.1 to 1 to determine that the ideal threshold value of 0.5 should be used as that corresponded with the lowest average error.

threshold = 0.1
GLM_error = rep(0, 5)
GLM_error_thresholds = rep(0, 10)
index = 1
while (threshold < 1) { #loop for threshold analysis
  GLM_error = rep(0, 5) 
  for (fold in 1:5) {#K-cross fold validation

    #training/testing set    
    set.seed(fold)
    num_samples = dim(GLM_Data)[1]
    sampling.rate = 0.8
    training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
    trainingSet = subset(GLM_Data[training, ])
    testing = setdiff(1:num_samples, training)
    testingSet = subset(GLM_Data[testing,])
    defaultW = getOption("warn")
    options(warn = -1)
    #set up model
    LogisticReg = glm(koi_disposition ~ .,
                      data = trainingSet,
                      family = binomial(logit))
    
    
    options(warn = defaultW)
    predictions = predict(LogisticReg, testingSet, type = "response")
    predictedLabels = rep(0, sizeTestSet)
    predictedLabels = ifelse(predictions > threshold, 'FALSE POSITIVE', 'CONFIRMED') #this parameter is tuned
    
    
    #determine error
    error = sum(predictedLabels != testingSet$koi_disposition)
    misclassificationRateLR = error / sizeTestSet
    GLM_error[fold] = misclassificationRateLR
  }
  
  GLM_error_thresholds[index] = mean(GLM_error)
  index = index + 1
  threshold = threshold + .1
}
order(GLM_error_thresholds) #Lowest error is threshold = .5
 [1]  5  4  6  3  7  2  8  9  1 10
AvgErrorGLM = GLM_error_thresholds[order(GLM_error_thresholds)[1]]
paste("Logistic Regression Error: ", round(AvgErrorGLM*100,2),"%",sep="")
[1] "Logistic Regression Error: 11.73%"
plot(
  x = 1:10 / 10,
  y = GLM_error_thresholds,
  main = "Avg Error over different thresholds for Logistic Regression Model",
  xlab = "Cutoff Threshold",
  ylab = "Misclassification Rate"
)
lines(x = 1:10 / 10, y = GLM_error_thresholds)

Logistic Regression has an error rate of 11.7%, making it the worst model yet. Its error is best when threshold = .5.

3.2) Models using scaled data:

The models below all require normalization of the data to be effective. This is an important step as all features need to be in the same scale. If not, the features with larger scales would dominate the model causing it to be inaccurate. To do this, we used the “scale” function to normalize all the dependent variables. We also changed the independent variable, koi_disposition, to be binary

3.2.2) Neural Network: Original Variables

The Neural Network model is built through functions, “neurons” that are then organized into layers. It is an advanced model that is ideally suited for complex problems as it requires significant computational resources. In addition, it is quite difficult to understand afterwards given the complexity of the math within the model. Our group was able to see the significant use of computational resources as our computer was unable to run the model. For this reason, the group did not use K-fold cross-validation in order to lower the computational power required to run the model, but in real-life application K-fold cross-validation should still be done.

When choosing the number of neurons and hidden layers it is important to find the right balance between accuracy and over-fitting. Our group manually adjusted the number of neurons and hidden layers, testing variations such as 4&2, 5&1, 6, 3&1, etc, until we found that two neurons and one hidden layer allowed the model to converge. The next step in the model was to choose the threshold value for classification. This was similar to choosing the threshold as we did in Logistic Regression. The ideal threshold of 0.5 was found by plotting the average error for each threshold ranging from 0.1 to 1 as that corresponded with the lowest misclassification rate.

#Create testing and training set
set.seed(123)
scaled_data = data.frame(scale(labeled_final[, 2:36])) #normalize data
scaled_data$koi_disposition = ifelse(labeled_final$koi_disposition == "CONFIRMED", 1, 0)
num_samples = dim(scaled_data)[1]
sampling.rate = 0.8
training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
trainingSet.norm = subset(scaled_data[training,])
testing = setdiff(1:num_samples, training)
testingSet.norm = subset(scaled_data[testing, ])
sizeTestSet = dim(testingSet.norm)[1]

#set up variables for neural network. Since 36 variables put too much computational strain, dimensionality reduction needed to take place. We chose to take only the features, not their errors, to reduce dimensionality.

koiDP.name = "koi_disposition"
numCols = dim(testingSet.norm)[2]
variable.names = colnames(testingSet.norm)[1:numCols - 1]
variable.names
 [1] "koi_period"        "koi_period_err1"   "koi_period_err2"   "koi_time0bk"      
 [5] "koi_time0bk_err1"  "koi_time0bk_err2"  "koi_impact"        "koi_impact_err1"  
 [9] "koi_impact_err2"   "koi_duration"      "koi_duration_err1" "koi_duration_err2"
[13] "koi_depth"         "koi_depth_err1"    "koi_depth_err2"    "koi_prad"         
[17] "koi_prad_err1"     "koi_prad_err2"     "koi_teq"           "koi_insol"        
[21] "koi_insol_err1"    "koi_insol_err2"    "koi_model_snr"     "koi_steff"        
[25] "koi_steff_err1"    "koi_steff_err2"    "koi_slogg"         "koi_slogg_err1"   
[29] "koi_slogg_err2"    "koi_srad"          "koi_srad_err1"     "koi_srad_err2"    
[33] "ra"                "dec"               "koi_kepmag"       
for (i in 1:length(variable.names)) {
  error = grepl("_err", variable.names[i], fixed = TRUE)
  if (error) {
    variable.names[i] = NA
  }
  
}
variable.names = na.omit(variable.names)
variable.names = variable.names[1:15]
#use formulaic library to create formula
nn.form <-
  create.formula(outcome.name = koiDP.name,
                 input.names = variable.names)
nn.form
$formula
koi_disposition ~ koi_period + koi_time0bk + koi_impact + koi_duration + 
    koi_depth + koi_prad + koi_teq + koi_insol + koi_model_snr + 
    koi_steff + koi_slogg + koi_srad + ra + dec + koi_kepmag
<environment: 0x00000150ef9e8ba8>

$inclusion.table

$interactions.table
#Fit neural network
nnModel1 = neuralnet(
  nn.form,
  data = trainingSet.norm,
  hidden = 2,
  
  linear.output = FALSE,
  act.fct = "logistic"
)
plot(nnModel1)


#Predict
predictedLabels = compute(nnModel1, testingSet.norm[, variable.names])

#tune threshold parameter
threshold = .1
NNErrors = rep(0, 10)
index = 1
while (threshold <= 1) {
  results = data.frame(actual = testingSet.norm$koi_disposition,
                       prediction = predictedLabels$net.result)
  results$roundedPrediction = ifelse(results$prediction > threshold, 1, 0)
  error = sum(results$actual != results$roundedPrediction)
  NNErrors[index] = error / sizeTestSet
  threshold = threshold + .1
  index = index + 1
}

order(NNErrors) #lowest misclass rate is at threshold = .5
 [1]  5  4  6  3  7  2  8  1  9 10
NeuralNetMisClassRate = NNErrors[order(NNErrors)[1]]
paste("Neural Net Misclassification Rate: ",round(100*NeuralNetMisClassRate,2),"%",sep="")
[1] "Neural Net Misclassification Rate: 10.02%"
plot(
  x = 1:10 / 10,
  y = NNErrors,
  main = "Avg Error over different thresholds for Neural Network",
  xlab = "Cutoff Threshold",
  ylab = "Misclassification Rate"
)
lines(x = 1:10 / 10, y = NNErrors)

In this run, NN had an error rate of 0.1002486, with an optimal cutoff threshold of .5. We will revisit the NN below, to look at ways to use PCA to capture variation while reducing necessary computational resources.

Neural Network: PCA w/13 Variables

Principal Component Analysis allows us to reduce dimensionality while capturing as much of the underlying variation in the data as possible. The first application of PCA we found was for Neural Networks, which are very computationally difficult. We decided to first use a PCA with 13 variables, the same number of variables as our original neural network. This would mean the same computational strain, but with features that are guaranteed to capture as much variation as possible with that number of variables.

res.pca.exoplanets = prcomp(identifiers_removed[2:36], center = TRUE, scale = TRUE) #perform PCA
summary(res.pca.exoplanets)
Importance of components:
                          PC1    PC2     PC3     PC4     PC5     PC6     PC7     PC8
Standard deviation     2.2712 2.1730 1.86778 1.72894 1.47639 1.37931 1.33622 1.25447
Proportion of Variance 0.1474 0.1349 0.09967 0.08541 0.06228 0.05436 0.05101 0.04496
Cumulative Proportion  0.1474 0.2823 0.38196 0.46737 0.52965 0.58400 0.63502 0.67998
                           PC9    PC10    PC11    PC12    PC13    PC14    PC15    PC16
Standard deviation     1.10767 1.04188 1.01171 0.98148 0.95840 0.93448 0.88263 0.78194
Proportion of Variance 0.03506 0.03101 0.02924 0.02752 0.02624 0.02495 0.02226 0.01747
Cumulative Proportion  0.71503 0.74605 0.77529 0.80282 0.82906 0.85401 0.87627 0.89374
                          PC17    PC18    PC19    PC20    PC21    PC22    PC23    PC24
Standard deviation     0.77439 0.75012 0.67294 0.63205 0.58189 0.55938 0.54423 0.48547
Proportion of Variance 0.01713 0.01608 0.01294 0.01141 0.00967 0.00894 0.00846 0.00673
Cumulative Proportion  0.91087 0.92695 0.93989 0.95130 0.96097 0.96991 0.97838 0.98511
                          PC25    PC26    PC27    PC28    PC29    PC30    PC31      PC32
Standard deviation     0.44729 0.37145 0.31152 0.23244 0.12420 0.10799 0.07019 2.036e-15
Proportion of Variance 0.00572 0.00394 0.00277 0.00154 0.00044 0.00033 0.00014 0.000e+00
Cumulative Proportion  0.99083 0.99477 0.99754 0.99909 0.99953 0.99986 1.00000 1.000e+00
                            PC33      PC34      PC35
Standard deviation     5.773e-16 5.354e-16 3.988e-16
Proportion of Variance 0.000e+00 0.000e+00 0.000e+00
Cumulative Proportion  1.000e+00 1.000e+00 1.000e+00
PoV <-
  res.pca.exoplanets$sdev ^ 2 / sum(res.pca.exoplanets$sdev ^ 2) #get proportions of variance
numPcas = 13
sum(PoV[1:numPcas]) #This gives us 83% explanation of variance. This is the number of variable in the original neural network, so we are using this to get a better NN with the same number of variables
[1] 0.8290598
newDataSet = data.frame(res.pca.exoplanets$x[, 1:numPcas])
newDataSet$label = identifiers_removed$koi_disposition
fviz_pca_var(res.pca.exoplanets, col.var = "contrib")

candidates_PCA = newDataSet[identifiers_removed$koi_disposition ==
                              "CANDIDATE", ] #separate out just the candidates
labeled_PCA = newDataSet[identifiers_removed$koi_disposition !=
                           "CANDIDATE", ]
labeled_PCA = droplevels(labeled_PCA)
candidates_PCA = droplevels(candidates_PCA)

Rerunning Neural Network Model

set.seed(123)
NN_labeled_PCA = labeled_PCA[, 1:numPcas]
NN_labeled_PCA$koi_disposition = ifelse(labeled_PCA$label == "CONFIRMED", 1, 0)
summary(scaled_data)
   koi_period      koi_period_err1   koi_period_err2     koi_time0bk      
 Min.   :-0.4049   Min.   :-0.1768   Min.   :-26.7574   Min.   :-0.64352  
 1st Qu.:-0.3844   1st Qu.:-0.1762   1st Qu.:  0.1590   1st Qu.:-0.43439  
 Median :-0.3277   Median :-0.1736   Median :  0.1736   Median :-0.37877  
 Mean   : 0.0000   Mean   : 0.0000   Mean   :  0.0000   Mean   : 0.00000  
 3rd Qu.:-0.1552   3rd Qu.:-0.1590   3rd Qu.:  0.1762   3rd Qu.: 0.07978  
 Max.   :11.9778   Max.   :26.7574   Max.   :  0.1768   Max.   :23.11291  
 koi_time0bk_err1   koi_time0bk_err2      koi_impact       koi_impact_err1  
 Min.   :-0.36586   Min.   :-31.75989   Min.   :-0.84878   Min.   :-0.2207  
 1st Qu.:-0.32745   1st Qu.:  0.00669   1st Qu.:-0.52855   1st Qu.:-0.2173  
 Median :-0.22068   Median :  0.22068   Median :-0.03305   Median :-0.2034  
 Mean   : 0.00000   Mean   :  0.00000   Mean   : 0.00000   Mean   : 0.0000  
 3rd Qu.:-0.00669   3rd Qu.:  0.32745   3rd Qu.: 0.37679   3rd Qu.:-0.1824  
 Max.   :31.75989   Max.   :  0.36586   Max.   :32.39182   Max.   : 8.2794  
 koi_impact_err2     koi_duration      koi_duration_err1  koi_duration_err2  
 Min.   :-37.0338   Min.   :-0.79008   Min.   :-0.46838   Min.   :-16.23186  
 1st Qu.: -0.3289   1st Qu.:-0.45677   1st Qu.:-0.40374   1st Qu.:  0.02716  
 Median :  0.2248   Median :-0.26098   Median :-0.28282   Median :  0.28282  
 Mean   :  0.0000   Mean   : 0.00000   Mean   : 0.00000   Mean   :  0.00000  
 3rd Qu.:  0.4915   3rd Qu.: 0.07348   3rd Qu.:-0.02716   3rd Qu.:  0.40374  
 Max.   :  0.5591   Max.   :19.80016   Max.   :16.23186   Max.   :  0.46838  
   koi_depth       koi_depth_err1     koi_depth_err2         koi_prad       
 Min.   :-0.3491   Min.   :-0.25725   Min.   :-49.18710   Min.   :-0.09248  
 1st Qu.:-0.3471   1st Qu.:-0.22666   1st Qu.:  0.09117   1st Qu.:-0.08857  
 Median :-0.3432   Median :-0.19211   Median :  0.19211   Median :-0.08497  
 Mean   : 0.0000   Mean   : 0.00000   Mean   :  0.00000   Mean   : 0.00000  
 3rd Qu.:-0.3030   3rd Qu.:-0.09117   3rd Qu.:  0.22666   3rd Qu.:-0.01473  
 Max.   : 9.5013   Max.   :49.18710   Max.   :  0.25725   Max.   :73.22898  
 koi_prad_err1      koi_prad_err2          koi_teq          koi_insol       
 Min.   :-0.06598   Min.   :-76.00748   Min.   :-1.2681   Min.   :-0.04894  
 1st Qu.:-0.06405   1st Qu.:  0.03814   1st Qu.:-0.6527   1st Qu.:-0.04872  
 Median :-0.06156   Median :  0.05676   Median :-0.2437   Median :-0.04762  
 Mean   : 0.00000   Mean   :  0.00000   Mean   : 0.0000   Mean   : 0.00000  
 3rd Qu.:-0.02127   3rd Qu.:  0.05834   3rd Qu.: 0.4011   3rd Qu.:-0.04094  
 Max.   :75.98152   Max.   :  0.05933   Max.   :15.5272   Max.   :65.69789  
 koi_insol_err1     koi_insol_err2      koi_model_snr       koi_steff       
 Min.   :-0.06739   Min.   :-68.06196   Min.   :-0.3877   Min.   :-3.71885  
 1st Qu.:-0.06706   1st Qu.:  0.04559   1st Qu.:-0.3714   1st Qu.:-0.48298  
 Median :-0.06516   Median :  0.05053   Median :-0.3514   Median : 0.06731  
 Mean   : 0.00000   Mean   :  0.00000   Mean   : 0.0000   Mean   : 0.00000  
 3rd Qu.:-0.05080   3rd Qu.:  0.05123   3rd Qu.:-0.2215   3rd Qu.: 0.49193  
 Max.   :69.45869   Max.   :  0.05136   Max.   : 9.1486   Max.   :12.43001  
 koi_steff_err1    koi_steff_err2        koi_slogg       koi_slogg_err1   
 Min.   :-3.0066   Min.   :-20.71031   Min.   :-9.8852   Min.   :-0.9095  
 1st Qu.:-0.8168   1st Qu.: -0.45354   1st Qu.:-0.2227   1st Qu.:-0.5830  
 Median : 0.2676   Median :  0.03832   Median : 0.2918   Median :-0.3753  
 Mean   : 0.0000   Mean   :  0.00000   Mean   : 0.0000   Mean   : 0.0000  
 3rd Qu.: 0.6430   3rd Qu.:  0.65961   3rd Qu.: 0.5374   3rd Qu.: 0.2109  
 Max.   :11.0913   Max.   :  2.09635   Max.   : 2.2474   Max.   :10.0123  
 koi_slogg_err2       koi_srad        koi_srad_err1      koi_srad_err2      
 Min.   :-9.1764   Min.   :-0.28040   Min.   :-0.35977   Min.   :-56.21894  
 1st Qu.:-0.8374   1st Qu.:-0.15546   1st Qu.:-0.22791   1st Qu.:  0.07577  
 Median : 0.1305   Median :-0.12554   Median :-0.10044   Median :  0.14915  
 Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.00000   Mean   :  0.00000  
 3rd Qu.: 0.6666   3rd Qu.:-0.06386   3rd Qu.: 0.00834   3rd Qu.:  0.17252  
 Max.   : 1.9621   Max.   :31.37715   Max.   :36.00187   Max.   :  0.21002  
       ra                dec            koi_kepmag      koi_disposition 
 Min.   :-2.58287   Min.   :-2.0123   Min.   :-5.4016   Min.   :0.0000  
 1st Qu.:-0.69660   1st Qu.:-0.8514   1st Qu.:-0.5886   1st Qu.:0.0000  
 Median : 0.04099   Median :-0.0363   Median : 0.1868   Median :0.0000  
 Mean   : 0.00000   Mean   : 0.0000   Mean   : 0.0000   Mean   :0.3761  
 3rd Qu.: 0.80352   3rd Qu.: 0.8106   3rd Qu.: 0.7616   3rd Qu.:1.0000  
 Max.   : 2.00621   Max.   : 2.3631   Max.   : 3.5325   Max.   :1.0000  
head(scaled_data)
num_samples = dim(scaled_data)[1]
sampling.rate = 0.8

#create training/testing sets
training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
trainingSet.norm.PCA = subset(NN_labeled_PCA[training,])
testing = setdiff(1:num_samples, training)
testingSet.norm.PCA = subset(NN_labeled_PCA[testing, ])
sizeTestSet = dim(testingSet.norm)[1]
label.name = "label"
variable.names = rep(0, numPcas)
numCols = dim(testingSet.norm.PCA)[2]
variable.names = colnames(testingSet.norm.PCA)[1:numCols - 1]
variable.names
 [1] "PC1"  "PC2"  "PC3"  "PC4"  "PC5"  "PC6"  "PC7"  "PC8"  "PC9"  "PC10" "PC11" "PC12"
[13] "PC13"
nn.form <-
  create.formula(outcome.name = koiDP.name,
                 input.names = variable.names)
nn.form
$formula
koi_disposition ~ PC1 + PC2 + PC3 + PC4 + PC5 + PC6 + PC7 + PC8 + 
    PC9 + PC10 + PC11 + PC12 + PC13
<environment: 0x00000150e847f948>

$inclusion.table

$interactions.table
#rerun neural network
nnModel2 = neuralnet(
  nn.form,
  data = trainingSet.norm.PCA,
  hidden = 2,
  linear.output = FALSE,
  act.fct = "logistic"
)
plot(nnModel2)

#Find results
predictedLabels = compute(nnModel2, testingSet.norm.PCA[, variable.names])

#Tune threshold
threshold = .1
NNErrors = rep(0, 10)
index = 1
while (threshold <= 1) {
  results = data.frame(actual = testingSet.norm.PCA$koi_disposition,
                       prediction = predictedLabels$net.result)
  results$roundedPrediction = ifelse(results$prediction > threshold, 1, 0)
  error = sum(results$actual != results$roundedPrediction)
  NNErrors[index] = error / sizeTestSet
  threshold = threshold + .1
  index = index + 1
}
order(NNErrors) #lowest misclassification rate is at threshold = .5
 [1]  5  4  6  7  3  2  8  1  9 10
PCA_13_v_NeuralNetMisClassRate = NNErrors[order(NNErrors)[1]]
paste("Neural Net (PCA, 13 var) Misclassification Rate: ",round(100*PCA_13_v_NeuralNetMisClassRate,2),"%",sep="")
[1] "Neural Net (PCA, 13 var) Misclassification Rate: 8.7%"
plot(
  x = 1:10 / 10,
  y = NNErrors,
  main = "Avg Error over thresholds for PCA Neural Network w/13 Vars",
  xlab = "Cutoff Threshold",
  ylab = "Misclassification Rate"
)
lines(x = 1:10 / 10, y = NNErrors)

We get abetter misclassification rate from the PCA version of NN using 13 variables of 8.7%, which is 1.33% less than our last Neural Network, and an optimal cutoff threshold of .5.

3.2.3) Neural Network: PCA w/20 Variables

Lastly (for NNs), we decided to see how many variables could capture 95% of variation. We found that 20 variables was sufficient; this means that 16 of our 36 variables, or 44.44% represent only 5% of variation.

#Last Neural Network, PCA, with 95% of variation explained
numVars = (dim((labeled_final))[2])
for (i in 1:(numVars)) {
  if (sum(PoV[1:i]) >= .95) {
    numPcas = i
    break
    
  }
}

sum(PoV[1:numPcas])
[1] 0.9513002
newDataSet = data.frame(res.pca.exoplanets$x[, 1:numPcas])

newDataSet$label = identifiers_removed$koi_disposition

candidates_PCA = newDataSet[identifiers_removed$koi_disposition ==
                              "CANDIDATE",] #separate out just the candidates
labeled_PCA = newDataSet[identifiers_removed$koi_disposition !=
                           "CANDIDATE",]
labeled_PCA = droplevels(labeled_PCA)
candidates_PCA = droplevels(candidates_PCA)
set.seed(123)
NN_labeled_PCA = labeled_PCA[, 1:numPcas]
NN_labeled_PCA$koi_disposition = ifelse(labeled_PCA$label == "CONFIRMED", 1, 0)
head(scaled_data)
#testing and training sets
num_samples = dim(scaled_data)[1]
sampling.rate = 0.8
training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
trainingSet.norm.PCA = subset(NN_labeled_PCA[training,])
testing = setdiff(1:num_samples, training)
testingSet.norm.PCA = subset(NN_labeled_PCA[testing, ])
sizeTestSet = dim(testingSet.norm)[1]
label.name = "label"
variable.names = rep(0, numPcas)

numCols = dim(testingSet.norm.PCA)[2]
variable.names = colnames(testingSet.norm.PCA)[1:numCols - 1]

nn.form <-
  create.formula(outcome.name = koiDP.name,
                 input.names = variable.names)
nn.form
$formula
koi_disposition ~ PC1 + PC2 + PC3 + PC4 + PC5 + PC6 + PC7 + PC8 + 
    PC9 + PC10 + PC11 + PC12 + PC13 + PC14 + PC15 + PC16 + PC17 + 
    PC18 + PC19 + PC20
<environment: 0x00000150f0b9b210>

$inclusion.table

$interactions.table
nnModel3 = neuralnet(
  nn.form,
  data = trainingSet.norm.PCA,
  hidden = 2,
  linear.output = FALSE,
  act.fct = "logistic"
)

plot(nnModel3)


#Make prediction
predictedLabels = compute(nnModel3, testingSet.norm.PCA[, variable.names])

index = 1
threshold = .1
NNErrors = rep(0, 10)
while (threshold <= 1) { #tune threshold parameter
  results = data.frame(actual = testingSet.norm.PCA$koi_disposition,
                       prediction = predictedLabels$net.result)
  results$roundedPrediction = ifelse(results$prediction > threshold, 1, 0)
  error = sum(results$actual != results$roundedPrediction)
  NNErrors[index] = error / sizeTestSet
  threshold = threshold + .1
  index = index + 1
}
NNErrors #lowest error is with threshold = .6
 [1] 0.10273405 0.09030655 0.08947804 0.08367854 0.08367854 0.08036454 0.08285004
 [8] 0.08864954 0.11930406 0.38856669
PCA_20_v_NeuralNetMisClassRate = NNErrors[order(NNErrors)[1]]
plot(
  x = 1:10 / 10,
  y = NNErrors,
  main = "Avg Error over thresholds for PCA Neural Network w/20 Vars",
  xlab = "Cutoff Threshold",
  ylab = "Misclassification Rate"
)
lines(x = 1:10 / 10, y = NNErrors)

As expected, this version has the lowest misclassification rate of 8.04%, which is 0.66% lower than the 13 variable PCA NN, and 1.99% lower than the original neural network. As you can see, however, we are reaching a point of diminishing returns; adding ~15% more variation only decreased the error rate by 0.66%

3.2.4) K-Nearest Neighbours

kNN works by computing the euclidean distances of the test features to training data points, known as “neighbours”. This model requires pre-processing because the distances from each data point must be in the same scale, therefore we first normalized the training and testing features. This model has an input parameter, k, which represents the number of neighbours considered. Both too-small and too-large values of k can be detrimental. To tune this parameter, we tested several values of k in a loop, selecting the k which resulted in the lowest misclassification rate.

numKs = 10
k_errors = rep(0, numKs)
for (ki in 1:numKs) {
  #tune k parameter
  avgErrors_fold = rep(0, 5)
  for (fold in 1:5) {
    #k-fold cross validation
    set.seed(fold)
    #make normalized training and testing sets
    scaled_data = data.frame(scale(labeled_final[, 2:36]))
    scaled_data$koi_disposition = ifelse(labeled_final$koi_disposition == "CONFIRMED", 1, 0)
    summary(scaled_data)
    head(scaled_data)
    num_samples = dim(scaled_data)[1]
    sampling.rate = 0.8
    training = sample(1:num_samples, sampling.rate * num_samples, replace = FALSE)
    trainingSet.norm = subset(scaled_data[training,])
    testing = setdiff(1:num_samples, training)
    testingSet.norm = subset(scaled_data[testing, ])
    sizeTestSet = dim(testingSet.norm)[1]
    
    trainingfeatures = subset(trainingSet.norm, select = c(-koi_disposition))
    traininglabels = trainingSet.norm$koi_disposition
    testingfeatures = subset(testingSet.norm, select = c(-koi_disposition))
    testinglabels = testingSet.norm$koi_disposition
    
    #fit model and predict
    predictedLabels = knn(trainingfeatures, testingfeatures, traininglabels, k =
                            ki)
    #determine error
    error = sum(predictedLabels != testingSet.norm$koi_disposition)
    
    misclassification_rate = error / sizeTestSet
    avgErrors_fold[fold] = misclassification_rate
  }
  
  k_errors[ki] = mean(avgErrors_fold)
}
print(order(k_errors))
 [1]  4  6  8  7 10  5  9  3  2  1
#The lowest average error (this run) is from the model with k = 4.
AvgError_best_knn = k_errors[order(k_errors)[1]]

KNN produces an error of 9.08%, with an optimal k-value of 4

3.2.5) K-Means Clustering

Clustering is an unsupervised model which uses randomly generated centroids and assigns every point to a centroid based on euclidean distance. The model then iterates to find the centroid locations which minimize the distances to the data points in the clusters. Since this model also requires calculation of euclidean distance, the normalized data set was also used here.

Since our data set was labelled, the supervised models will likely yield a better misclassification rate than k-Means clustering. We include clustering in case the algorithm found unforeseen relationships in the unlabelled data features.

#scale full data set. 
set.seed(123)
scaled_data = data.frame(scale(labeled_final[, 2:36]))
scaled_data$koi_disposition = ifelse(labeled_final$koi_disposition == "CONFIRMED", 2, 1)
num_samples = dim(scaled_data)[1]
features = subset(scaled_data, select = c(-koi_disposition))
#fit the model
kclustering = kmeans(features, centers = 2, nstart = 25)
#visualize the clusters
fviz_cluster(kclustering, data = features)

error = sum(scaled_data$koi_disposition != kclustering$cluster)
misclassification_rate = error / dim(scaled_data)[1]
AvgErrorClustering = misclassification_rate
if (AvgErrorClustering > .5) {
  AvgErrorClustering = 1 - AvgErrorClustering#since clustering does not know which cluster is CONFIRMED and which is FALSE POSITIVE, they can be flipped
}

Unsurprisingly, clustering has the largest error, of 41.83%; given this is an unsupervised method and our data has labels.

4) Select Best Model

error_output = data.frame(
  "Model" = c(
    "Decision Tree",
    "GLM",
    "Random Forest",
    "SVM",
    "Neural Net",
    "KNN",
    "Clustering",
    "PCA 13 Var NN",
    "PCA 20 Var NN",
    "XGBoost"
  ),
  "Misclassification Rate" = c(
    AvgErrorDT,
    AvgErrorGLM,
    AvgErrorRF,
    AvgErrorSVM,
    NeuralNetMisClassRate,
    AvgError_best_knn,
    AvgErrorClustering,
    PCA_13_v_NeuralNetMisClassRate,
    PCA_20_v_NeuralNetMisClassRate,
    AvgErrorXGB
  )
)
print(error_output)

XGBoost has the lowest misclassification rate. We will remake this model using the full dataset.

#5 Make predictions

Remake the XGBoost model using the full dataset:

   xgData = data.matrix(labeled_final)
library(xgboost)
Warning: package ‘xgboost’ was built under R version 4.1.2
    xgData[, 1] = ifelse(xgData[, 1] == 2, 1, 0)
   
    xgBoostModelFinal = xgboost(
      data = xgData[, 2:36],
      label = xgData[, 1],
      max.depth = 6,
      eta = .22,
      nrounds = 100,
      verbose = 0,
      objective = "binary:logistic",
      eval_metric="error"
    )
    xgPredict = data.matrix(candidates_final)
    xgPredict[, 1] = ifelse(xgPredict[, 1] == 2, 1, 0)
    
   

Predict labels of candidates dataset, and write it to file

 #make predictions
BoostPredictions = predict(xgBoostModelFinal, data.matrix(candidates_final)[, 2:36])
BoostPredictionsRounded = ifelse(BoostPredictions > .4, 1, 0)
predictedLabels = ifelse(BoostPredictionsRounded == 1, "FALSE POSITIVE", "CONFIRMED")
candidates_final$koi_disposition = predictedLabels
head(candidates_final)
write.csv(candidates_final, "labeledCandidates.csv")
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIEV4b3BsYW5ldCBDbGFzc2lmaWNhdGlvbiBQcm9ibGVtIA0KDQpGb3IgdGhpcyBwcm9qZWN0LCB3ZSBkZWNpZGVkIHRvIHBlcmZvcm0gY2xhc3NpZmljYXRpb24gb24gb3VyIGRhdGEgc2V0LiBDbGFzc2lmaWNhdGlvbiBpcyBhIHdheSB0byBwcmVkaWN0IGEgbGFiZWwsIGluIG91ciBjYXNlIGNhdGVnb3JpY2FsLCBiYXNlZCBvbiB0aGUgY2hhcmFjdGVyaXN0aWNzIG9mIHRoZSBkYXRhLiBUaGUNCg0KDQpUaGUgb2JqZWN0aXZlIG9mIG91ciBwcm9qZWN0IGlzIHRvIHVzZSBtYWNoaW5lIGxlYXJuaW5nIGNsYXNzaWZpY2F0aW9uIG1vZGVscyB0byBkZXRlcm1pbmUgaWYgYSBnaXZlbiBvYnNlcnZhdGlvbiBvZiBhIHN0YXIgc2hvdWxkIGJlIGNsYXNzaWZpZWQgYXMgYW4gZXhvcGxhbmV0IChhIHBsYW5ldCBvdXRzaWRlIG91ciBzb2xhciBzeXN0ZW0pLiBUaGUgb3JpZ2luYWwgZGF0YSBzZXQgY29tZXMgZnJvbSB0aGUgW0tlcGxlciBTcGFjZSBPYnNlcnZhdG9yeV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9uYXNhL2tlcGxlci1leG9wbGFuZXQtc2VhcmNoLXJlc3VsdHMpLCBhbmQgZGV0YWlscyA5LDU2NCAgb2JzZXJ2YXRpb25zIG9mIHBvdGVudGlhbCBleG9wbGFuZXRzLCBhbG9uZyB3aXRoIDUwIGRlc2NyaXB0aXZlIGZlYXR1cmVzIHJhbmdpbmcgZnJvbSBpZGVudGlmaWVycyB0byBzcGVjaWZpYyBtZWFzdXJlbWVudHMuIFRoZSBjb2x1bW4g4oCca29pX2Rpc3Bvc2l0aW9u4oCdIGxhYmVsbGVkIGVhY2ggb2YgdGhlIHBvdGVudGlhbCBleG9wbGFuZXRzIGFzIGVpdGhlciDigJxDT05GSVJNRUQs4oCdIOKAnEZBTFNFIFBPU0lUSVZFLCIgb3IgIkNBTkRJREFURSIuIFRob3NlIGxhYmVsZWQgIkNBTkRJREFURSIgaGF2ZSBub3QgeWV0IGJlZW4gZGV0ZXJtaW5lZCB0byBiZSBleG9wbGFuZXRzIG9yIG5vdDsgdGhlIGdvYWwgb2Ygb3VyIGFuYWx5c2lzIHdpbGwgYmUgdG8gbGFiZWwgdGhlbSBhcyAiQ09ORklSTUVEIiBvciAiRkFMU0UgUE9TSVRJVkUuIiBOT1RFOiBJbiB0aGlzIGNvbnRleHQsICJmYWxzZSBwb3NpdGl2ZSIgZG9lcyBub3QgaW5kaWNhdGUgYSBmYWxzZSBwb3NpdGl2ZSBvdXRwdXQgZnJvbSBvdXIgbW9kZWwuIEluc3RlYWQsIGl0IHJlZmVycyB0byB0aGUgb3JpZ2luYWwgb2JzZXJ2YXRpb24sIHdoaWNoIHdhcyBjb25zaWRlcmVkIGEgY2FuZGlkYXRlLCB0byBoYXZlIGJlZW4gZmFsc2VseSBpZGVudGlmaWVkIGFzIGEgY2FuZGlkYXRlLg0KDQpUbyBzb2x2ZSB0aGlzIHByb2JsZW0sIHdlIHdpbGwgY2Fycnkgb3V0IHRoZSBmb2xsb3dpbmcgcHJvY2VzczoNCg0KMS4gTG9hZCwgcHJlcHJvY2VzcywgYW5kIGNsZWFuIHVwIGRhdGENCjIuIERhdGEgdmlzdWFsaXphdGlvbiBhbmQgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcw0KMy4gQ3JlYXRpb24gYW5kIHRlc3Rpbmcgb2YgY2FuZGlkYXRlIG1vZGVsczoNCiAgICArIDMuMSkgTW9kZWxzIHVzaW5nIG5vbi1zY2FsZWQgZGF0YToNCiAgICAgICAgLSAzLjEuMSkgRGVjaXNpb24gVHJlZSwgUmFuZG9tIEZvcmVzdCwgYW5kIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUNCiAgICAgICAgLSAzLjEuMikgWEdCb29zdA0KICAgICAgICAtIDMuMS4zKSBMb2dpc3RpYyBSZWdyZXNzaW9uDQogICAgKyAzLjIpIE1vZGVscyB1c2luZyBzY2FsZWQgZGF0YToNCiAgICAgICAgLSAzLjIuMSkgTmV1cmFsIE5ldHdvcms6IE9yaWdpbmFsIFZhcmlhYmxlcw0KICAgICAgICAtIDMuMi4yKSBOZXVyYWwgTmV0d29yazogUENBIHcvMTMgVmFyaWFibGVzDQogICAgICAgIC0gMy4yLjMpIE5ldXJhbCBOZXR3b3JrOiBQQ0Egdy8yMCBWYXJpYWJsZXMNCiAgICAgICAgLSAzLjIuNCkgSy1OZWFyZXN0IE5laWdoYm91cnMNCiAgICAgICAgLSAzLjIuNSkgSy1NZWFucyBDbHVzdGVyaW5nDQo0LiBTZWxlY3QgYmVzdCBtb2RlbA0KNS4gVXNlIGJlc3QgbW9kZWwgdG8gbWFrZSBwcmVkaWN0aW9ucw0KDQojIDEuIERhdGEgTG9hZGluZywgUHJlLVByb2Nlc3NpbmcgYW5kIENsZWFuIFVwDQoNCkxvYWQgTGlicmFyaWVzOg0KYGBge3J9DQpsaWJyYXJ5KE1hdHJpeCkgICAgI2V4dHJhIE1hdHJpeCBmdW5jdGlvbmFsaXR5DQpsaWJyYXJ5KHJwYXJ0KSAgICAgICNEZWNpc2lvbiB0cmVlcw0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpI3JhbmRvbSBmb3Jlc3QNCmxpYnJhcnkoY2xhc3MpICAgICAgI0tOTg0KbGlicmFyeShlMTA3MSkgICAgICAjbWlzYy4gc3RhdHMgZnVuY3Rpb25hcnkNCmxpYnJhcnkoeGdib29zdCkgICAgI1hHQm9vc3QgQWxnb3JpdGhtDQpsaWJyYXJ5KEZOTikgICAgICAgICNLTk4NCmxpYnJhcnkoZmFjdG9leHRyYSkgI1BDQSBhbmQgY2x1c3RlcmluZw0KbGlicmFyeShnZ3Bsb3QyKSAgICAjZXh0cmEgcGxvdHRpbmcgZnVuY3Rpb25hbGl0eQ0KbGlicmFyeShjb3JycGxvdCkgICAjZXh0cmEgcGxvdHRpbmcgZnVuY3Rpb25hbGl0eQ0KI3Zpc3VhbGl6YXRpb24gcGFja2FnZXMNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KERpYWdyYW1tZVIpICNwbG90dGluZyBmb3IgWEdCb29zdA0KbGlicmFyeShmb3JtdWxhaWMpICNhdXRvbWF0ZWQgZm9ybXVsYSBjcmVhdGlvbg0KbGlicmFyeShuZXVyYWxuZXQpICNOZXVyYWwgTmV0d29ya3MNCmBgYA0KDQoNClRoZSBmaXJzdCBzdGVwIHdhcyB0byBsb2FkIHRoZSBkYXRhLCBhbmQgY29udmVydCBvdXIgb3V0cHV0IHZhcmlhYmxlLCAia29pX2Rpc3Bvc2l0aW9uIiwgdG8gYSBmYWN0b3INCmBgYHtyfQ0KI3JlYWQgZGF0YQ0Kb3JpZ2luYWxLZXBwbGVyRGF0YSA9IHJlYWQuY3N2KCJjdW11bGF0aXZlLmNzdiIpICNyZWFkIGRhdGENCmZhY3RvcmVkX2tlcHBsZXJfZGF0YSA9IG9yaWdpbmFsS2VwcGxlckRhdGENCmZhY3RvcmVkX2tlcHBsZXJfZGF0YSRrb2lfZGlzcG9zaXRpb24gPSBmYWN0b3Iob3JpZ2luYWxLZXBwbGVyRGF0YSRrb2lfZGlzcG9zaXRpb24pICNmYWN0b3IgY2hhcmFjdGVyIGRhdGENCmZhY3RvcmVkX2tlcHBsZXJfZGF0YSRrb2lfcGRpc3Bvc2l0aW9uID0gZmFjdG9yKG9yaWdpbmFsS2VwcGxlckRhdGEka29pX3BkaXNwb3NpdGlvbikNCmhlYWQoZmFjdG9yZWRfa2VwcGxlcl9kYXRhKQ0KYGBgDQpPdXIgbmV4dCBzdGVwIHdhcyB0byBjb25kdWN0IGZvcm1zIG9mIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBvbiBvdXIgNDkgcG9zc2libGUgaW5wdXQgdmFyaWFibGVzLiBEaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gaXMgaW1wb3J0YW50IGZvciBvdXIgbGFyZ2UgZGF0YSBzZXQgdG8gcmVkdWNlIHRoZSBjb21wdXRhdGlvbmFsIHJlcXVpcmVtZW50cyBvZiBtb2RlbHMgYW5kIHJlZHVjZSBwb3NzaWJsZSBvdmVyZml0dGluZyBvciBiaWFzIGZyb20gdW4taW1wb3J0YW50IGZlYXR1cmVzLiBGaXJzdCwgd2UgcmVtb3ZlZCBzZXZlcmFsIGVudGlyZWx5IGVtcHR5IGNvbHVtbnMgKHdpdGggdmFsdWVzIG9mIDAgb3IgTkEpLCB1bmlxdWUgcm93IGlkZW50aWZpZXJzIChvYnNlcnZhdGlvbiBudW1iZXJzKSwgYXMgd2VsbCBhcyBzZXZlcmFsIHdoaWNoIGNvdWxkIG9ubHkgYmUgZmlsbGVkIG9uY2UgdGhlIG91dGNvbWUgb2YgdGhlIG9ic2VydmF0aW9uIHdhcyBhbHJlYWR5IGtub3duLiBGb3IgZXhhbXBsZSwg4oCca2VwbGVyX25hbWXigJ0gaXMgdGhlIG5hbWUgZ2l2ZW4gdG8gYSBjb25maXJtZWQgZXhvcGxhbmV0LCBzbyBpdCB3YXMgcmVtb3ZlZCBmcm9tIHRoZSBkYXRhIHNldC4gVGhlcmUgd2VyZSBubyB1bmlxdWUgb3V0bHlpbmcgZGF0YSBwb2ludHMgdGhhdCBoYWQgdG8gYmUgZXhwbG9yZWQgZnVydGhlci4gDQoNCmBgYHtyfQ0KI3JlbW92ZSBhbGwtTkEgY29sdW1uczoNCnJlbW92ZV9LT0lfdGVjaF9mYWN0b3JlZCA9IHN1YnNldChmYWN0b3JlZF9rZXBwbGVyX2RhdGEsIHNlbGVjdCA9IC1jKGtvaV90ZXFfZXJyMSkpICNyZW1vdmUgDQpyZW1vdmVfS09JX3RlY2hfZmFjdG9yZWQgPSBzdWJzZXQocmVtb3ZlX0tPSV90ZWNoX2ZhY3RvcmVkLCBzZWxlY3QgPSAtYyhrb2lfdGVxX2VycjIpKQ0KcmVtb3ZlX05BcyA9IG5hLmV4Y2x1ZGUocmVtb3ZlX0tPSV90ZWNoX2ZhY3RvcmVkKSAjcmVtb3ZlIGFueSByb3dzIHdpdGggTkFzIHJlbWFpbmluZw0KDQojUmVtb3ZlIHVuaXF1ZSBpZGVudGlmaWVycyBhbmQgcm93cyB0aGF0IGNhbiBvbmx5IGJlIGtub3duIGFmdGVyIHRoZSBzdGF0dXMgb2YgYSBwbGFuZXQgaXMga25vd24uIFRoZXkgdGhlcmVmb3JlIGFyZSBub3QgdXNlZnVsIGlucHV0cyB0byBkZXRlcm1pbmUgdGhlIHN0YXR1cyBvZiBhIGNhbmRpZGF0ZS4NCmlkZW50aWZpZXJzX3JlbW92ZWQgPSBzdWJzZXQoDQogIHJlbW92ZV9OQXMsDQogIHNlbGVjdCA9IC1jKA0KICAgIHJvd2lkLA0KICAgIGtlcGlkLA0KICAgIGtlcG9pX25hbWUsDQogICAga2VwbGVyX25hbWUsDQogICAga29pX3BkaXNwb3NpdGlvbiwNCiAgICBrb2lfc2NvcmUNCiAgKQ0KKQ0KDQojQWxzbyBuZWVkcyB0byByZW1vdmUgZmxhZ3MsIHRoZXNlIGNhbiBvbmx5IGJlIGtub3duIGFmdGVyIHN0YXR1cyBpcyBjb25maXJtZWQNCmlkZW50aWZpZXJzX3JlbW92ZWQgPSBzdWJzZXQoDQogIGlkZW50aWZpZXJzX3JlbW92ZWQsDQogIHNlbGVjdCA9IC1jKA0KICAgIGtvaV9mcGZsYWdfc3MsDQogICAga29pX2ZwZmxhZ19lYywNCiAgICBrb2lfZnBmbGFnX2NvLA0KICAgIGtvaV9mcGZsYWdfbnQsDQogICAga29pX3RjZV9wbG50X251bSwNCiAgICBrb2lfdGNlX2RlbGl2bmFtZQ0KICApDQopDQoNCiNOb3csIHdlIHdpbGwgc2VwYXJhdGUgaW4gbGFiZWxlZCBhbmQgdW5sYWJlbGVkIGRhdGEuIFRoZSBsYWJlbGVkIGRhdGEgd2lsbCBiZSB1c2VkIHRvIHRyYWluIG91ciBtb2RlbDsgb25jZSB0aGUgYmVzdCBtb2RlbCBpcyBzZWxlY3RlZCwgd2Ugd2lsbCB1c2UgaXQgdG8gcHJlZGljdCB0aGUgbGFiZWxzIG9mIHRoZSB1bmxhYmVsZWQgZGF0YXNldA0KY2FuZGlkYXRlc19maW5hbCA9IGlkZW50aWZpZXJzX3JlbW92ZWRbaWRlbnRpZmllcnNfcmVtb3ZlZCRrb2lfZGlzcG9zaXRpb24gPT0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNBTkRJREFURSIsXSAjc2VwYXJhdGUgb3V0IGp1c3QgdGhlIGNhbmRpZGF0ZXMNCmxhYmVsZWRfZmluYWwgPSBpZGVudGlmaWVyc19yZW1vdmVkW2lkZW50aWZpZXJzX3JlbW92ZWQka29pX2Rpc3Bvc2l0aW9uICE9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDQU5ESURBVEUiLF0NCmxhYmVsZWRfZmluYWwgPSBkcm9wbGV2ZWxzKGxhYmVsZWRfZmluYWwpICAgICAgIA0KY2FuZGlkYXRlc19maW5hbCA9IGRyb3BsZXZlbHMoY2FuZGlkYXRlc19maW5hbCkNCmhlYWQobGFiZWxlZF9maW5hbCkNCmhlYWQoY2FuZGlkYXRlc19maW5hbCkNCmBgYA0KV2UgYXJlIGxlZnQgd2l0aCB0d28gZGF0YXNldHM6DQoNCiAtIGNhbmRpZGF0ZXNfZmluYWwsIHdoaWNoIGhhcyAxNzcyIGRhdGEgcG9pbnRzIGFuZCAzNSBmZWF0dXJlcywgd2hpY2ggd2lsbCBiZSB3aGF0IHdlIHVzZSB0aGUgYmVzdCBtb2RlbCBvbg0KIC0gbGFiZWxlZF9maW5hbCwgd2hpY2ggaGFzIDYwMzEgZGF0YSBwb2ludHMgYW5kIDM1IGZlYXR1cmVzLCB3aGljaCB3aWxsIGJlIHdoYXQgd2UgdXNlIHRvIHRyYWluIGFuZCB0ZXN0IGNhbmRpZGF0ZSBtb2RlbHMsIGFzIHdlbGwgYXMgYnVpbGQgb3VyIGZpbmFsIG1vZGVsLg0KDQojIDIpIERhdGEgVmlzdWFsaXphdGlvbiBhbmQgRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcw0KU2VsZWN0IHRoZSB2YXJpYWJsZXMgdG8gYmUgdXNlZDoNCmBgYHtyfQ0KI0FsbCB2YXJpYWJsZXMgaGF2ZSBlcnJvcnMgLSBmb3IgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcyB3ZSB3aWxsIG9ubHkgYmUgbG9va2luZyBhdCB0aGUgdmFyaWFibGVzIHRoZW1zZWx2ZXMsIG5vdCB0aGVpciBlcnJvcnMNCnZhcmlhYmxlc29ubHkgPSBsYWJlbGVkX2ZpbmFsICU+JQ0KICBzZWxlY3Qoa29pX3BlcmlvZCwga29pX3RpbWUwYmssIGtvaV9pbXBhY3QsIGtvaV9kdXJhdGlvbiwga29pX2RlcHRoLCBrb2lfcHJhZCwga29pX3RlcSwga29pX2luc29sLCBrb2lfbW9kZWxfc25yLCBrb2lfc3RlZmYsIGtvaV9zbG9nZywga29pX3NyYWQsIHJhLCBkZWMsIGtvaV9rZXBtYWcpDQpgYGANCg0KDQpDcmVhdGUgYSBjb3JyZWxhdGlvbiBoZWF0IG1hcCB3aXRoIG9ubHkgdGhlIHZhcmlhYmxlcyAoZXhjbHVkaW5nIHRoZSBlcnJvcnMpLg0KYGBge3J9DQpjb3JtYXQgPC0gcm91bmQoY29yKHZhcmlhYmxlc29ubHkpLDIpDQpoZWFkKGNvcm1hdCkNCg0KbWVsdGVkX2Nvcm1hdCA8LSBtZWx0KGNvcm1hdCkNCmhlYWQobWVsdGVkX2Nvcm1hdCkNCg0KZ2dwbG90KGRhdGEgPSBtZWx0ZWRfY29ybWF0LCBhZXMoeD1WYXIxLCB5PVZhcjIsIGZpbGw9dmFsdWUpKSArIA0KICBnZW9tX3RpbGUoKQ0KYGBgDQogRGVmaW5pbmcgc2lnbmlmaWNhbnQgY29ycmVsYXRpb25zIGFzIHRob3NlIGdyZWF0ZXIgdGhhbiAuNCwgdGhlcmUgYXJlIGEgZmV3OyB0aGlzIHdpbGwgbmVlZCB0byBiZSBkZWFsdCB3aXRoIGluIExvZ2lzdGljIFJlZ3Jlc3Npb24sIGJ1dCBzaG91bGQgbm90IGFmZmVjdCBvdGhlciBtb2RlbHMuIA0KDQojIyMjIFBsb3RzIGJ5IEZBTFNFIFBPU0lUSVZFIGFuZCBDT05GSVJNRUQNCg0KV2Ugd2lsbCBub3cgb2JzZXJ2ZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHNldmVyYWwgdmFyaWFibGVzIGFjcm9zcyBmYWxzZSBwb3NpdGl2ZSBhbmQgY29uZmlybWVkIGV4b3BsYW5ldHMuIFRoaXMgd2lsbCBoZWxwIGRldGVybWluZSB3aGVyZSB0aGVyZSBpcyBhIGNsZWFyIHBhdHRlcm4gaW4gY2VydGFpbiB2YXJpYWJsZXMuDQpgYGB7cn0NCmdncGxvdChkYXRhID0gbGFiZWxlZF9maW5hbCkgKw0KICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGtvaV9wZXJpb2QsIHkgPSBrb2lfdGltZTBiaykpICsgDQogIGZhY2V0X3dyYXAofiBrb2lfZGlzcG9zaXRpb24sIG5yb3cgPSAyKQ0KYGBgDQpGb3Iga29pX3BlcmlvZCBhbmQga29pX3RpbWUwYmssIHRoZSBkaXN0cmlidXRpb25zIG9mIHRoZSB2YXJpYWJsZXMgc2VlbSBzaW1pbGFycywgd2l0aCBzb21lIG91dGxpZXJzLiBUaGF0IGJlaW5nIHNhaWQsIEZBTFNFIFBPU0lUSVZFcyBjYW4gaGF2ZSBzaWduaWZpY2FudGx5IGhpZ2hlciBrb2lfcGVyaW9kcyB0aGFuIENPTkZJUk1FRHMNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBsYWJlbGVkX2ZpbmFsKSArDQogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0ga29pX2ltcGFjdCwgeSA9IGtvaV9kdXJhdGlvbikpICsgDQogIGZhY2V0X3dyYXAofiBrb2lfZGlzcG9zaXRpb24sIG5yb3cgPSAyKQ0KYGBgDQpUaGUgcGxvdCBvZiBrb2lfaW1wYWN0IHZzLiBrb2lfZHVyYXRpb24gc2hvd3MgY2xlYXIgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGVpciBkaXN0cmlidXRpb25zIC0gQ09ORklSTUVEcyBoYXZlIGEgc2lnbmlmaWNhbnRseSBzbWFsbCByYW5nZSBmb3IgYm90aCB2YXJpYWJsZXMsIGFsdGhvdWdoIHRob3NlIGNhbmRpZGF0ZXMgdGhhdCBmYWxsIGluc2lkZSB0aGF0IHJhbmdlIG1heSBiZSBkaWZmaWN1bHQgdG8gY2xhc3NmaXkuDQpgYGB7cn0NCmdncGxvdChkYXRhID0gbGFiZWxlZF9maW5hbCkgKw0KICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGtvaV9kZXB0aCwgeSA9IGtvaV9wcmFkKSkgKyANCiAgZmFjZXRfd3JhcCh+IGtvaV9kaXNwb3NpdGlvbiwgbnJvdyA9IDIpDQpgYGANCmtvaV9kZXB0aCBzaG93cyBhIGNsZWFyIGRpZmZlcmVudDogaGlnaCBrb2lfZGVwdGhzIGFsbW9zdCBhbHdheXMgaW5kaWNhdGUgRkFMU0UgUE9TSVRJVkVzDQpgYGB7cn0NCmdncGxvdChkYXRhID0gbGFiZWxlZF9maW5hbCkgKw0KICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGtvaV90ZXEsIHkgPSBrb2lfaW5zb2wpKSArIA0KICBmYWNldF93cmFwKH4ga29pX2Rpc3Bvc2l0aW9uLCBucm93ID0gMikNCmBgYA0Ka29pX3RlcSBoYXMgYSBzaWduaWZpY2FudGx5IHNtYWxsZXIgcmFuZ2UgZm9yIENPTkZJUk1FRCBkYXRhIHBvaW50cywgYW5kIEtPSV9pbnNvbCBzdGF5cyBjbG9zZXIgdG8gemVyby4gSWYgd2UgbG9vayBhdCANCmBgYHtyfQ0Kc3VtbWFyeShsYWJlbGVkX2ZpbmFsJGtvaV9pbnNvbCkNCnN1bW1hcnkobGFiZWxlZF9maW5hbCRrb2lfdGVxKQ0KYGBgDQpUaGlzIGNvbmZpcm1zIHRoYXQgdGhlcmUgYXJlIHZlcnkgbGFyZ2UgdmFsdWVzIGZvciBGQUxTRSBQT1NJVElWRVMgdGhhdCBhcmUgc2lnbmlmaWNhbnRseSBvdXRzaWRlIHRoZSBub3JtYWwgZGF0YSByYW5nZS4gSXQgd2lsbCBiZSBlYXN5IHRvIGNsYXNzaWZ5IHRoZXNlIGFzIEZBTFNFIFBPU0lUSVZFcy4gV2UgY2FuIGV4cGxvcmUgdGhlc2UgZ3JhcGhzIHdpdGggYSBzbWFsbGVyIHJhbmdlLCB0byBleGNsdWRlIHRoZSBvdXRsaWVyczoNCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGxhYmVsZWRfZmluYWxbbGFiZWxlZF9maW5hbCRrb2lfaW5zb2w8MjUwMDAwLF0pICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBrb2lfdGVxLCB5ID0ga29pX2luc29sKSkgKyANCiAgZmFjZXRfd3JhcCh+IGtvaV9kaXNwb3NpdGlvbiwgbnJvdyA9IDIpDQpgYGANClRoaXMgc2hvd3MgdGhhdCBmb3IgdmFsdWVzIG9mIGtvaV9pbnNvbCA8IDI1MDAwIGFuZCBrb2lfdGVxPDMwMDAsIGl0IGJlY29tZXMgZGlmZmljdWx0IHRvIGlkZW50aWZ5IHdoZXRoZXIgYSBnaXZlbiBkYXRhIHBvaW50IGlzIEZBTFNFIFBPU0lUSVZFIG9yIENPTkZJUk1FRC4NCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBsYWJlbGVkX2ZpbmFsKSArDQogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0ga29pX21vZGVsX3NuciwgeSA9IGtvaV9zdGVmZikpICsgDQogIGZhY2V0X3dyYXAofiBrb2lfZGlzcG9zaXRpb24sIG5yb3cgPSAyKQ0KYGBgDQpPbmNlIGFnYWluLCBGQUxTRSBQT1NJVElWRVMgc2hvdyBzaWduaWZpY2FudGx5IG1vcmUgdmFyaWFiaWxpdHkgb24gYm90aCBheGVzIHRoYW4gQ09ORklSTUVEcw0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGxhYmVsZWRfZmluYWwpICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBrb2lfc2xvZ2csIHkgPSBrb2lfc3JhZCkpICsgDQogIGZhY2V0X3dyYXAofiBrb2lfZGlzcG9zaXRpb24sIG5yb3cgPSAyKQ0KYGBgDQpGQUxTRSBQT1NJVElWRXMgY2FuIGhhdmUgc2lnbmlmaWNhbnRseSBsb3dlciBrb2lfc2xvZ2dzIGFuZCBoaWdoZXIga29pX3NyYWRzIHRoYW4gQ09ORklSTUVEcw0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGxhYmVsZWRfZmluYWwpICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSByYSwgeSA9IGRlYykpICsNCiAgZmFjZXRfd3JhcCggfiBrb2lfZGlzcG9zaXRpb24sIG5yb3cgPSAyKQ0KYGBgDQpMYXN0bHksIGZvciByYSBhbmQgZGVjLCB0aGVyZSBpcyBubyBjbGVhciBkaWZmZXJlbnQgaW4gZGlzdHJpYnV0aW9uIGZvciBDT05GSVJNRUQgb3IgRkFMU0UgUE9TSVRJVkUuDQoNCk92ZXJhbGwsIGFuIGVhc3kgd2F5IHRvIGRldGVybWluZSBGQUxTRSBQT1NJVElWRXMgdGVuZHMgdG8gYmUgdG8gbG9vayBmb3IgZGF0YSBwb2ludHMgb3V0c2lkZSBhIGdpdmVuIHJhbmdlLiBJbnNpZGUgdGhhdCByYW5nZSwgdGhlIGRldGVybWluYXRpb24gb2Ygd2hldGhlciBhbiBvYnNlcnZhdGlvbiBpcyBhIGV4b3BsYW5ldCBvciBub3QgYmVjb21lcyBtb3JlIGRpZmZpY3VsdC4gDQoNCiMgMy4gQ3JlYXRpb24gYW5kIHRlc3Rpbmcgb2YgY2FuZGlkYXRlIG1vZGVscw0KDQpUdG8gY2xhc3NpZnkgdGhlIGRhdGEgYW5kIGRldGVybWluZSB3aGljaCBvYnNlcnZhdGlvbiBhcmUsIGluIGZhY3QsIHBsYW5ldHMsIG91ciBncm91cCBmaXJzdCBoYWQgdG8gZGV0ZXJtaW5lIHdoaWNoIG1vZGVsIG1vc3QgYWNjdXJhdGVseSBwcmVkaWN0ZWQgb3VyIGxhYmVsZWQgZGF0YSBzZXQuIFdlIGZpcnN0IGV4YW1pbmVkIG1vZGVscyB0aGF0IGRpZCBub3QgcmVxdWlyZSBzY2FsaW5nLCB0aGVzZSBpbmNsdWRlZDogRGVjaXNpb24gVHJlZSwgUmFuZG9tIEZvcmVzdCwgWEdCb29zdCwgTG9naXN0aWMgUmVncmVzc2lvbiwgYW5kIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIChTVk0pLiBOZXh0LCB3ZSBleGFtaW5lZCBtb2RlbHMgdGhhdCByZXF1aXJlZCBzY2FsaW5nLCB0aGVzZSBpbmNsdWRlZDogS05OLCBOZXVyYWwgTmV0d29yayAod2l0aCBhbmQgd2l0aG91dCBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzKSwgYW5kIEstbWVhbnMgY2x1c3RlcmluZy4gVG8gY2hvb3NlIHRoZSBiZXN0IG1vZGVsIGZvciBvdXIgZGF0YSBzZXQsIG91ciBncm91cCBkZWNpZGVkIHRvIGxvb2sgYXQgd2hpY2ggbW9kZWwgcHJvZHVjZWQgdGhlIGxvd2VzdCBtaXNjbGFzc2lmaWNhdGlvbiByYXRlLiBIb3dldmVyLCBpdCBzaG91bGQgYmUgbm90ZWQgdGhhdCBpbiByZWFsLWxpZmUgYXBwbGljYXRpb24gYSBudW1iZXIgb2Ygb3RoZXIgbWV0cmljcyBzaG91bGQgYmUgY29uc2lkZXJlZCBhcyB3ZWxsLiBUaGVzZSBpbmNsdWRlLCBidXQgYXJlIG5vdCBsaW1pdGVkIHRvLCBwcmVjaXNpb24gYW5hbHlzaXMgKFBSIGN1cnZlcyksIHJlY2FsbCwgUk9DLUFVQywgYW5kIEYxIFNjb3Jlcy4gDQoNCldoZW4gcnVubmluZyB0aGUgdmFyaW91cyBtb2RlbHMsIG91ciBncm91cCBpbmNvcnBvcmF0ZWQgSy1mb2xkIGNyb3NzLXZhbGlkYXRpb24uIFRoZSBiZW5lZml0IG9mIHRoaXMgYXBwcm9hY2ggaXMgdGhhdCBpdCBhbGxvd3MgdGhlIG1vZGVsIHRvIGJlY29tZSBtb3JlIGdlbmVyYWxpemVkLCBoZWxwaW5nIHdpdGggb3Zlci1maXR0aW5nIGNvbmNlcm5zLiBJbiBhZGRpdGlvbiwgd2UgdGFrZSB0aGUgYXZlcmFnZSByZXN1bHQgb2YgZml2ZSBtaXNjbGFzc2lmaWNhdGlvbiByYXRlcyBmb3IgZWFjaCBtb2RlbCwgc2lnbmlmaWNhbnRseSBsb3dlcmluZyB0aGUgY2hhbmNlIHRoYXQgYSBnaXZlbiBtaXNjbGFzc2lmaWNhdGlvbiByYXRlIGlzIHByb2R1Y2VkIG9ubHkgYnkgY2hhbmNlIGR1ZSB0byBhIHNwZWNpZmljIHRlc3RpbmcvdHJhaW5pbmcgc2V0LiBUaGlzIGFsbG93cyB1cyB0byBiZSBtb3JlIGNvbmZpZGVudCB0aGF0IHRoZSBtb2RlbCB3aXRoIHRoZSBsb3dlc3QgbWlzY2xhc3NpZmljYXRpb24gcmF0ZSB0cnVseSBpcyB0aGUgYmVzdC4gRHVlIHRvIGNvbXB1dGF0aW9uYWwgbGltaXRzLCB3ZSBkZWNpZGVkIHRvIHVzZSBhIEsgdmFsdWUgb2YgNSBhcyB3ZSBiZWxpZXZlZCB0aGF0IHdvdWxkIGJlIGFkZXF1YXRlIGZvciBvdXIgcHVycG9zZXMuIEZpbmFsbHksIHdoZW4gZGVjaWRpbmcgdGhlIHByb3BvcnRpb24gb2YgdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0LCB3ZSBkZWNpZGVkIHRvIGZvbGxvdyBjbGFzcyBzdGFuZGFyZHMgd2l0aCA4MCUgdHJhaW5pbmcgc2V0IGFuZCAyMCUgdGVzdGluZyBzZXQuIE91ciBzcGVjaWZpYyBkYXRhIHNldCBpcyBmYWlybHkgbGFyZ2Ugd2l0aCAgfjYsMDMxIHJvd3M7IHdlIGJlbGlldmUgaGF2aW5nIH40ODAwIHJvd3MgdG8gdHJhaW4gYW5kIH4xLDIwMCByb3dzIHRvIHRlc3QgaXMgbGFyZ2UgZW5vdWdoIGZvciBlYWNoIHB1cnBvc2UuIA0KDQojIyAzLjEpIE1vZGVscyB1c2luZyBub24tc2NhbGVkIGRhdGE6DQoNCiMjIyAzLjEuMSkgRGVjaXNpb24gVHJlZSwgUmFuZG9tIEZvcmVzdCwgYW5kIFNWTQ0KICAgICAgICANClRoZSBnb2FsIG9mIGEgZGVjaXNpb24gdHJlZSBpcyB0byBtYWtlIHNwbGl0dGluZyBkZWNpc2lvbnMgb24gdGhlIGRhdGEsIGluIGFuIGVmZm9ydCB0byBtaW5pbWl6ZSB0aGUgbGVhc3Qgc3F1YXJlcywgdGh1cyBjcmVhdGluZyBhIHRyZWUtbGlrZSBzdHJ1Y3R1cmUuIFRoZXNlIG1vZGVscyBhcmUgdXNlZnVsIGFzIGEgc3RhcnRpbmcgcG9pbnQgYmVjYXVzZSB0aGV5IGFyZSBlYXN5IHRvIGludGVycHJldCBhcyB0aGUgcGxvdCBjYW4gZGlzcGxheSB3aGljaCB2YXJpYWJsZXMgaGF2ZSB0aGUgaGlnaGVzdCBpbXBvcnRhbmNlIGluIHRoZSB0cmVlLiBOb3JtYWxpemF0aW9uIGFuZCBvdGhlciBkYXRhIGNsZWFuaW5nIGlzIGFsc28gbm90IHJlcXVpcmVkIGZvciB0aGlzIG1vZGVsLiAgQWRkaW5nIG1vcmUgZGVwdGggdG8gdGhlIHRyZWUgY2FuIHJlZHVjZSB0aGUgZml0dGluZyBlcnJvciB0byB0aGUgZGF0YSwgYnV0IGl0IGNhbiBsZWFkIHRvIG92ZXJmaXR0aW5nIHRoZSBtb2RlbC4gQXMgYSByZXN1bHQsIHRoZSBjb21wbGV4aXR5IHBhcmFtZXRlciwgY3AsIG11c3QgYmUgdHVuZWQgdG8gZW5zdXJlIHRoYXQgdGhlIG9wdGltYWwgZGVwdGgtdG8tZml0IG9mIHRoZSBtb2RlbCBpcyB1c2VkLiBBcyB0aGUgY3AgdmFsdWUgZGVjcmVhc2VzLCBzbyBkb2VzIHRoZSByZWxhdGl2ZSBlcnJvciBpbiB0aGUgbW9kZWwuIFdlIGF1dG9tYXRpY2FsbHkgcHJ1bmUgb3VyIGRlY2lzaW9uIHRyZWUgdG8gc2VsZWN0IHRoZSBjcCB3aGVyZSB0aGUgY2hhbmdlIGluIGVycm9yIGlzIGxlc3MgdGhhbiAwLjA1OyB0aGlzIGlzIG91ciBmaXJzdCBleGFtcGxlIG9mIHBhcmFtZXRlciB0dW5pbmcuIA0KDQpSYW5kb20gZm9yZXN0IG1vZGVscyBhcmUgbW9yZSBhY2N1cmF0ZSBhbmQgcm9idXN0IGJ1dCBoYXJkZXIgdG8gaW50ZXJwcmV0IHRoYW4gYSBzaW5nbGUgdHJlZS4gVGhlIG1vZGVsIGNyZWF0ZXMgbWFueSBkZWNpc2lvbiB0cmVlcyB3aXRoIGRpZmZlcmVudCByYW5kb21pemVkIGxlYXJuaW5nIGFuZCB0ZXN0aW5nIHNldHMsIHRoZW4gdGhlIHRyZWVzIOKAnHZvdGXigJ0gb3Ig4oCcYXZlcmFnZeKAnSB0aGVpciByZXN1bHRzIHRvIGRldGVybWluZSB0aGUgcmVzdWx0YW50IHJhbmRvbSBmb3Jlc3QgbW9kZWwuIFRob3VnaCB0aGUgbW9kZWwgaXMgbm90IGFzIGludGVycHJldGFibGUgYXMgYSBzaW5nbGUgdHJlZSBhbmQgaXQgaXMgbW9yZSBkaWZmaWN1bHQgdG8gdW5kZXJzdGFuZCB0aGUgc2lnbmlmaWNhbmNlIG9mIGEgc2luZ2xlIHZhcmlhYmxlLCBpdCB3aWxsIHJlc3VsdCBpbiBsb3dlciBtaXNjbGFzc2lmaWNhdGlvbiByYXRlLiBUaGUgbnVtYmVyIG9mIHRyZWVzIGluIHRoZSBmb3Jlc3QgaXMgYSBwYXJhbWV0ZXIgdGhhdCBuZWVkcyB0byBiZSB0dW5lZCBpbiB0aGlzIG1vZGVsLiBBcyB0aGUgbnVtYmVyIG9mIHRyZWVzIGluY3JlYXNlcywgdGhlIGVycm9yIGRlY3JlYXNlcyBleHBvbmVudGlhbGx5LCByZWFjaGluZyBhbiBhc3ltcHRvdGUgb2YgZXJyb3IuIA0KDQpTVk0gbW9kZWxzIGFyZSBzdXBlcnZpc2VkIGxlYXJuaW5nIG1vZGVscyB0aGF0IHVzZSB0aGUgZGF0YSBwb2ludHMgdG8gY3JlYXRlIGEgbGluZSB0byBzZXBhcmF0ZSB0aGUgZGF0YS4gVGhpcyBzZXBhcmF0aW9uIHRoZW4gZGVjaWRlcyB0aGUgYmluYXJ5IGNsYXNzaWZpY2F0aW9uIGZvciBlYWNoIGRhdGEgcG9pbnQuIFdoaWxlIHRoaXMgbW9kZWwgY2FuIGJlIGluY3JlZGlibHkgdmVyc2F0aWxlIGFuZCByb2J1c3QgYWdhaW5zdCBvdXRsaWVycyBhbmQgaW5hY2N1cmF0ZSBkYXRhLCBpdCBtYXkgbm90IGJlIGFzIGFjY3VyYXRlIGlmIHRoZXJlIGlzIG11Y2ggb3ZlcmxhcCBiZXR3ZWVuIHRoZSBkYXRhLiANCg0KQ29kZSBmb3IgZGVjaXNpb24gdHJlZSwgcmFuZG9tIGZvcmVzdCwgYW5kIFNWTToNCmBgYHtyfQ0KZGVjVHJlZV9FcnJvciA9IHJlcCgwLCA1KQ0KUkZfZXJyb3IgPSByZXAoMCwgNSkNClNWTV9lcnJvciA9IHJlcCgwLCA1KQ0KDQpmb3IgKGZvbGQgaW4gMTo1KSB7DQogICNzZXQgdXAgay1jcm9zc2ZvbGQgdmFsaWRhdGlvbg0KICBzZXQuc2VlZChmb2xkKQ0KICANCiAgDQogICNzcGxpdCBkYXRhIGludG8gdGVzdGluZyBhbmQgdHJhaW5pbmcgc2V0cw0KICBudW1fc2FtcGxlcyA9IGRpbShsYWJlbGVkX2ZpbmFsKVsxXQ0KICBzYW1wbGluZy5yYXRlID0gMC44DQogIHRyYWluaW5nID0gc2FtcGxlKDE6bnVtX3NhbXBsZXMsIHNhbXBsaW5nLnJhdGUgKiBudW1fc2FtcGxlcywgcmVwbGFjZSA9IEZBTFNFKQ0KICB0cmFpbmluZ1NldCA9IHN1YnNldChsYWJlbGVkX2ZpbmFsW3RyYWluaW5nLF0pDQogIHRlc3RpbmcgPSBzZXRkaWZmKDE6bnVtX3NhbXBsZXMsIHRyYWluaW5nKQ0KICB0ZXN0aW5nU2V0ID0gc3Vic2V0KGxhYmVsZWRfZmluYWxbdGVzdGluZywgXSkNCiAgDQogIA0KICAjRGVjaXNpb24gdHJlZQ0KICBkZWNUcmVlTW9kZWwgPSBycGFydChrb2lfZGlzcG9zaXRpb24gfiAuLCBkYXRhID0gdHJhaW5pbmdTZXQpDQogIA0KICAjQXV0b21hdGljYWxseSBzZWxlY3QgdGhlIHN0b3BwaW5nIHBvaW50IHdoZXJlIGNwIG5vIGxvbmdlciBpbXByb3ZlcyBlcnJvciBieSAwLjA1DQogIGVycm9ycyA9IGRlY1RyZWVNb2RlbCRjcHRhYmxlWywgM10NCiAgZGVjVHJlZUNoYW5nZUVycm9yID0gYygwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwKQ0KICBmb3IgKGkgaW4gMTo4KSB7DQogICAgZGVjVHJlZUNoYW5nZUVycm9yW2ldID0gZXJyb3JzW2kgKyAxXSAtIGVycm9yc1tpXQ0KICB9DQogIGRlY1RyZWVDaGFuZ2VFcnJvcg0KICBmb3IgKGkgaW4gMTo5KSB7DQogICAgaWYgKGFicyhkZWNUcmVlQ2hhbmdlRXJyb3JbaV0pIDwgMC4wNSkgew0KICAgICAgc3RvcEluZGV4ID0gaQ0KICAgICAgYnJlYWsNCiAgICB9DQogIH0NCiAgY3BzID0gZGVjVHJlZU1vZGVsJGNwdGFibGVbLCAxXQ0KICBjcFN0b3AgPSBjcHNbc3RvcEluZGV4XQ0KICANCiANCiAgcHJ1bmVkRGVjVHJlZU1vZGVsID0gcnBhcnQ6OnBydW5lKGRlY1RyZWVNb2RlbCwgY3AgPSBjcFN0b3ApICNQcnVuZSBkZWNpc2lvbiB0cmVlDQogIGRlY1RyZWVQcmVkaWN0aW9ucyA9IHByZWRpY3QocHJ1bmVkRGVjVHJlZU1vZGVsLCB0ZXN0aW5nU2V0LCB0eXBlID0gImNsYXNzIikgI21ha2UgcHJlZGljdGlvbnMNCiAgI0RldGVybWluZSBkZWNpc2lvbiB0cmVlIGVycm9yDQogIHNpemVUZXN0U2V0ID0gZGltKHRlc3RpbmdTZXQpWzFdDQogIGRlY1RyZWVNb2RlbF9lcnJvciA9IHN1bShkZWNUcmVlUHJlZGljdGlvbnMgIT0gdGVzdGluZ1NldCRrb2lfZGlzcG9zaXRpb24pDQogIG1pc2NsYXNzaWZpY2F0aW9uUmF0ZURlY1RyZWUgPSBkZWNUcmVlTW9kZWxfZXJyb3IgLyBzaXplVGVzdFNldA0KICBkZWNUcmVlX0Vycm9yW2ZvbGRdID0gbWlzY2xhc3NpZmljYXRpb25SYXRlRGVjVHJlZQ0KDQogICNSYW5kb20gRm9yZXN0DQogIFJhbmRGb3Jlc3RNb2RlbCA9IHJhbmRvbUZvcmVzdChrb2lfZGlzcG9zaXRpb24gfiAuLCBkYXRhID0gdHJhaW5pbmdTZXQpDQogIHByZWRpY3RlZExhYmVscyA9IHByZWRpY3QoUmFuZEZvcmVzdE1vZGVsLCB0ZXN0aW5nU2V0KQ0KICANCiAgI0RldGVybWluZSBSYW5kb20gRm9yZXN0IEVycm9yDQogIHNpemVUZXN0U2V0ID0gZGltKHRlc3RpbmdTZXQpWzFdDQogIGVycm9yID0gc3VtKHByZWRpY3RlZExhYmVscyAhPSB0ZXN0aW5nU2V0JGtvaV9kaXNwb3NpdGlvbikNCiAgbWlzY2xhc3NpZmljYXRpb25fcmF0ZVJhbmRGb3Jlc3QgPSBlcnJvciAvIHNpemVUZXN0U2V0DQogIFJGX2Vycm9yW2ZvbGRdID0gbWlzY2xhc3NpZmljYXRpb25fcmF0ZVJhbmRGb3Jlc3QNCiAgDQogICNTVk0gTW9kZWwNCiAgc3ZtTW9kZWwgPSBzdm0oa29pX2Rpc3Bvc2l0aW9uIH4gLiwgZGF0YSA9IHRyYWluaW5nU2V0LCBrZXJuZWwgPSAibGluZWFyIikNCiAgcHJlZGljdGVkbGFiZWxzU1ZNID0gcHJlZGljdChzdm1Nb2RlbCwgdGVzdGluZ1NldCkNCiAgI0RldGVybWluZSBTVk0gZXJyb3INCiAgZXJyb3JTVk0gPSBzdW0ocHJlZGljdGVkbGFiZWxzU1ZNICE9IHRlc3RpbmdTZXQka29pX2Rpc3Bvc2l0aW9uKQ0KICBtaXNjbGFzc2lmaWNhdGlvbl9yYXRlU1ZNID0gZXJyb3JTVk0gLyBzaXplVGVzdFNldA0KICBTVk1fZXJyb3JbZm9sZF0gPSBtaXNjbGFzc2lmaWNhdGlvbl9yYXRlU1ZNDQp9DQojVGFrZSBhdmVyYWdlIG9mIGVycm9ycyBmcm9tIGVhY2ggZm9sZCB0byBkZXRlcm1pbmUgYXZlcmFnZSBlcnJvciBmb3IgZWFjaCBtb2RlbA0KQXZnRXJyb3JEVCA9IG1lYW4oZGVjVHJlZV9FcnJvcikNCkF2Z0Vycm9yUkYgPSBtZWFuKFJGX2Vycm9yKQ0KQXZnRXJyb3JTVk0gPSBtZWFuKFNWTV9lcnJvcikNCnBhc3RlKCJEVCBFcnJvcjogIiwgcm91bmQoMTAwKkF2Z0Vycm9yRFQsMiksIiUiLHNlcCA9ICIiKQ0KcGFzdGUoIlJGIEVycm9yOiAiLCByb3VuZCgxMDAqQXZnRXJyb3JSRiwyKSwiJSIsc2VwID0gIiIpDQpwYXN0ZSgiU1ZNIEVycm9yOiAiLCByb3VuZCgxMDAqQXZnRXJyb3JTVk0sMiksIiUiLHNlcCA9ICIiKQ0KYGBgDQpBbGwgdGhyZWUgbW9kZWxzIGdpdmUgbG93IG1pc2NsYXNzaWZpY2F0aW9uIHJhdGVzIDwxMiUsIGJ1dCBSYW5kb20gRm9yZXN0IGdpdmVzIHRoZSBiZXN0IG1pc2NsYXNzaWZpY2F0aW9uIHJhdGUgYXQgb25seSA2Ljc0JS4NCg0KIyMjIDMuMS4yKSBYR0Jvb3N0DQoNClhHQm9vc3Qgc3RhbmRzIGZvciDigJxleHRyZW1lIGdyYWRpZW50IGJvb3N0aW5n4oCdIGFuZCBpcyBhbiBvcGVuLXNvdXJjZSB0cmVlIGxlYXJuaW5nIGFsZ29yaXRobSBzaW1pbGFyIHRvIHJhbmRvbSBmb3Jlc3RzIHRoYXQgaXMgYWxzbyB3aWRlbHkgdXNlZCBpbiBpbmR1c3RyeS4gVGhpcyBtb2RlbCBzZWVrcyB0byBtaW5pbWl6ZSBhbiBvYmplY3RpdmUgZnVuY3Rpb24gcmVwcmVzZW50aW5nIG1vZGVsIGNvbXBsZXhpdHkgYW5kIGxvc3MgKGVycm9yKSwgdXNpbmcgYSBncmFkaWVudCBkZXNjZW50IGFsZ29yaXRobSB0byBtaW5pbWl6ZSBsb3NzIHdoZW4gYWRkaW5nIG5ldyBtb2RlbHMuIFRoaXMgaXMga25vd24gYXMgdHJlZSBib29zdGluZzsgcmFuZG9tIGZvcmVzdCBtb2RlbHMgZGlmZmVyIGJlY2F1c2UgdGhleSB1c2UgYSB0cmVlIGJhZ2dpbmcgYWxnb3JpdGhtLCBwb3NzaWJseSBsZWFkaW5nIHRvIGRpZmZlcmVudCBtb2RlbCBhY2N1cmFjaWVzLiANCg0KWEdCb29zdCBvdXRwdXRzIGEgcHJvYmFiaWxpdHkgYmV0d2VlbiAwIGFuZCAxLCByYXRoZXIgdGhhbiBhIGJpbmFyeSBjbGFzc2lmaWNhdGlvbi4gRm9yIHRoaXMgcmVhc29uLCBpdCBpcyBuZWNlc3NhcnkgdG8gZGV0ZXJtaW5lIHRoZSAidGhyZXNob2xkIiB3aGVyZSBhIHZhbHVlIHN0b3BzIGJlaW5nIGEgRkFMU0UgUE9TSVRJVkUsIGFuZCBzdGFydCBiZWluZyBhIENPTkZJUk1FRC4gUXVlc3Rpb25pbmcgdGhlIGNsYXNzaWZpY2F0aW9uIHRocmVzaG9sZCB3aGljaCBpcyBidWlsdCBpbnRvIG91ciBtb2RlbHMgY2FuIGhlbHAgdXMgZGV2ZWxvcCBtb3JlIGFjY3VyYXRlIG1vZGVscy4gRm9yIGV4YW1wbGUsIGFyYml0cmFyaWx5IGFzc3VtaW5nIHRoYXQgdGhlIHRocmVzaG9sZCBmb3IgY2xhc3NpZnlpbmcgYmFzZWQgb24gb3VyIFhHQm9vc3QgbW9kZWwgd2FzIGV4YWN0bHkgMC41IGNvdWxkIGhhdmUgcmVzdWx0ZWQgaW4gYSBoaWdoZXIgbWlzY2xhc3NpZmljYXRpb24gcmF0ZSBpZiB3ZSB3ZXJlIHRvIGNvbnNpZGVyIGFsbCBwb3NzaWJsZSB0aHJlc2hvbGRzLiBBcyBhIHJlc3VsdCwgd2UgY29uZHVjdGVkIHRocmVzaG9sZCBhbmFseXNpcyBvbiBhbGwgb3VyIG1vZGVscyB0aGF0IG91dHB1dCBwcm9iYWJpbGl0aWVzIGJ5IGxvb3BpbmcgdGhyb3VnaCBhbGwgY2xhc3NpZmljYXRpb24gdGhyZXNob2xkcyBhdCAwLjEgaW5jcmVtZW50cywgcGxvdHRlZCB0aGVtIGFnYWluc3QgdGhlaXIgcmVzcGVjdGl2ZSBtaXNjbGFzc2lmaWNhdGlvbiByYXRlcywgYW5kIHRvb2sgdGhlIG1pbmltdW0gYXMgdGhlIG9wdGltYWwuIFRoZSBmaXJzdCBtb2RlbCB0aGF0IHdlIGhhdmUgZG9uZSB0aGlzIGZvciBpcyBYR0Jvb3N0LiAgDQoNCmBgYHtyfQ0KeGdiLnNldC5jb25maWcodmVyYm9zaXR5ID0gMCkNCnRocmVzaG9sZCA9IDAuMQ0KWEdCb29zdF9lcnJvcl90aHJlc2hvbGRzID0gcmVwKDAsIDEwKQ0KaW5kZXggPSAxDQp3aGlsZSAodGhyZXNob2xkIDwgMSkgew0KICAjbG9vcCB0aGF0IHJlcnVucyB0aGUgYWxnb3JpdGhtIHdpdGggaW5jcmVtZW50cyBvZiAwLjEgaW4gdGhlIHRocmVzaG9sZA0KICBYR0JfZXJyb3IgPSByZXAoMCwgNSkNCiAgZm9yIChmb2xkIGluIDE6NSkgew0KICAgICNrIGNyb3NzLWZvbGQgdmFsaWRhdGlvbg0KICAgIHNldC5zZWVkKGZvbGQpDQogICAgDQogICAgbnVtX3NhbXBsZXMgPSBkaW0obGFiZWxlZF9maW5hbClbMV0NCiAgICANCiAgICAjY3JlYXRlIHRlc3RpbmcgYW5kIHRyYWluaW5nIHNldA0KICAgIHNhbXBsaW5nLnJhdGUgPSAwLjgNCiAgICB0cmFpbmluZyA9IHNhbXBsZSgxOm51bV9zYW1wbGVzLCBzYW1wbGluZy5yYXRlICogbnVtX3NhbXBsZXMsIHJlcGxhY2UgPSBGQUxTRSkNCiAgICB0cmFpbmluZ1NldCA9IHN1YnNldChsYWJlbGVkX2ZpbmFsW3RyYWluaW5nLF0pDQogICAgdGVzdGluZyA9IHNldGRpZmYoMTpudW1fc2FtcGxlcywgdHJhaW5pbmcpDQogICAgdGVzdGluZ1NldCA9IHN1YnNldChsYWJlbGVkX2ZpbmFsW3Rlc3RpbmcsIF0pDQogICAgDQogICAgI1hHQm9vc3QgbW9kZWwNCiAgICB4Z1RyYWluID0gZGF0YS5tYXRyaXgodHJhaW5pbmdTZXQpDQogICAgeGdUcmFpblssIDFdID0gaWZlbHNlKHhnVHJhaW5bLCAxXSA9PSAyLCAxLCAwKQ0KICAgDQogICAgeGdCb29zdE1vZGVsID0geGdib29zdCgNCiAgICAgIGRhdGEgPSB4Z1RyYWluWywgMjozNl0sDQogICAgICBsYWJlbCA9IHhnVHJhaW5bLCAxXSwNCiAgICAgIG1heC5kZXB0aCA9IDYsDQogICAgICBldGEgPSAuMjIsDQogICAgICBucm91bmRzID0gMTAwLA0KICAgICAgdmVyYm9zZSA9IDAsDQogICAgICBvYmplY3RpdmUgPSAiYmluYXJ5OmxvZ2lzdGljIiwNCiAgICAgIGV2YWxfbWV0cmljPSJlcnJvciINCiAgICApDQogICAgeGdUZXN0ID0gZGF0YS5tYXRyaXgodGVzdGluZ1NldCkNCiAgICB4Z1Rlc3RbLCAxXSA9IGlmZWxzZSh4Z1Rlc3RbLCAxXSA9PSAyLCAxLCAwKQ0KICAgIA0KICAgICNtYWtlIHByZWRpY3Rpb25zDQogICAgQm9vc3RQcmVkaWN0aW9ucyA9IHByZWRpY3QoeGdCb29zdE1vZGVsLCBkYXRhLm1hdHJpeCh0ZXN0aW5nU2V0KVssIDI6MzZdKQ0KICAgIEJvb3N0UHJlZGljdGlvbnNSb3VuZGVkID0gaWZlbHNlKEJvb3N0UHJlZGljdGlvbnMgPiB0aHJlc2hvbGQsIDEsIDApICNjb252ZXJ0IHByb2JhYmlsaXRpZXMgdG8gb3VwdXRzIG9mIDEgb3IgMCBiYXNlZCBvbiB3aGV0aGVyIHRoZXkgYXJlIGdyZWF0ZXIgdGhhbiB0aGUgdGhyZXNob2xkIC0gdGhpcyBpcyB3aGVyZSBwYXJhbWV0ZXIgdHVuaW5nIG9jY3Vycy4NCiAgICBCb29zdEVycm9yID0gc3VtKEJvb3N0UHJlZGljdGlvbnNSb3VuZGVkICE9IHhnVGVzdFssIDFdKQ0KICAgIG1pc2NsYXNzaWZpY2F0aW9uUmF0ZUJvb3N0ID0gQm9vc3RFcnJvciAvIGRpbSh4Z1Rlc3QpWzFdDQogICAgWEdCX2Vycm9yW2ZvbGRdID0gbWlzY2xhc3NpZmljYXRpb25SYXRlQm9vc3QNCiAgfQ0KICBYR0Jvb3N0X2Vycm9yX3RocmVzaG9sZHNbaW5kZXhdID0gbWVhbihYR0JfZXJyb3IpI2F2ZXJhZ2UgdGhlIGVycm9yIGZyb20gYWxsIGZvbGRzDQogIGluZGV4ID0gaW5kZXggKyAxDQogIHRocmVzaG9sZCA9IHRocmVzaG9sZCArIC4xDQp9DQp4Z2JQbG90ID0geGdiLnBsb3QudHJlZShtb2RlbCA9IHhnQm9vc3RNb2RlbCwNCiAgICAgICAgICAgICAgICAgICAgICAgIHRyZWVzID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHJlbmRlciA9IFRSVUUpDQp4Z2JQbG90ICNwbG90IGFuIGV4YW1wbGUgdHJlZQ0KDQoNCiNEZXRlcm1pbmUgY29ycmVjdCB0aHJlc2hvbGQgdmFsdWUNClhHQm9vc3RfZXJyb3JfdGhyZXNob2xkcyNzaG93cyB0aGUgYXZlcmFnZSBlcnJvciBhdCBlYWNoIHRocmVzaG9sZA0Kb3JkZXIoWEdCb29zdF9lcnJvcl90aHJlc2hvbGRzKSAjTG93ZXN0IGVycm9yIGlzIHRocmVzaG9sZCA9IC40DQpBdmdFcnJvclhHQiA9IFhHQm9vc3RfZXJyb3JfdGhyZXNob2xkc1sob3JkZXIoWEdCb29zdF9lcnJvcl90aHJlc2hvbGRzKVsxXSldICNjaG9zZSB0aGUgdGhyZXNob2xkIHdpdGggdGhlIGxvd2VzdCBlcnJvciBhcyB0aGUgb25lIHdlIHVzZQ0KcGFzdGUoIlhHQm9vc3QgRXJyb3I6ICIsIHJvdW5kKEF2Z0Vycm9yWEdCKjEwMCwyKSwgIiUiLCBzZXAgPSAiIikNCiNwbG90IHRoZSBlcnJvciB0aHJlc2hvbGRzDQpwbG90KA0KICB4ID0gMToxMCAvIDEwLA0KICB5ID0gWEdCb29zdF9lcnJvcl90aHJlc2hvbGRzLA0KICBtYWluID0gIkF2ZyBFcnJvciBvdmVyIGRpZmZlcmVudCB0aHJlc2hvbGRzIGZvciBYR0Jvb3N0IE1vZGVsIiwNCiAgeGxhYiA9ICJDdXRvZmYgVGhyZXNob2xkIiwNCiAgeWxhYiA9ICJNaXNjbGFzc2lmaWNhdGlvbiBSYXRlIg0KKQ0KbGluZXMoeCA9IDE6MTAgLyAxMCwgeSA9IFhHQm9vc3RfZXJyb3JfdGhyZXNob2xkcykNCmBgYA0KWEdCb29zdCBnaXZlcyBhbiBlcnJvciByYXRlIG9mIGByIHJvdW5kKEF2Z0Vycm9yWEdCKjEwMCwyKWAlLiBUaGlzIGlzIHNpZ25pZmljYW50bHkgYmV0dGVyIHRoYW4gYW55IG1vZGVsIHJ1biBzbyBmYXIuICBUaGUgdGhyZXNob2xkIGFuYWx5c2lzIGdyYXBoIGFib3ZlIHNob3dzIHRoYXQgdGhlIGJlc3QgbWlzY2xhc3NpZmljYXRpb24gcmF0ZSBmb3IgWEdCb29zdCBpcyBhY3R1YWxseSBhdCB0aGUgdGhyZXNob2xkID0gLjQuIA0KIA0KICAgDQojIyMgMy4xLjUpIExvZ2lzdGljIFJlZ3Jlc3Npb24NCg0KTG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMgaXMgYSBzdXBlcnZpc2VkIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobSB0aGF0IGJ1aWxkcyBhIHJlZ3Jlc3Npb24gbW9kZWwgdG8gcHJlZGljdCB0aGUgY2xhc3NpZmljYXRpb24gYnkgYXNzaWduaW5nIGRhdGEgZW50cmllcyB0byBiaW5hcnkgdmFsdWVzLCBiYXNlZCBvbiB0aGUgU2lnbW9pZCBmdW5jdGlvbi4gV2hlbiBwZXJmb3JtaW5nIGxvZ2lzdGljIHJlZ3Jlc3Npb24sIGl0IGlzIGltcG9ydGFudCB0byBjb25zaWRlciB0aGUgcHJvYmxlbXMgdGhhdCBhcmlzZSBmcm9tIG11bHRpY29sbGluZWFyaXR5IHdoaWNoIGNhbiBjYXVzZSB1bnN0YWJsZSBlc3RpbWF0ZXMgYW5kIGluYWNjdXJhY3kuIEZvciB0aGlzIHJlYXNvbiwgd2UgZGVjaWRlZCB0byBmaXJzdCByZW1vdmUgYWxsIG1ham9yIG11bHRpY29sbGluZWFyaXR5IGZyb20gdGhlIG1vZGVsLiANCg0KYGBge3J9DQojQ2hlY2sgZm9yIG11bHRpY29sbGluZWFyaXR5DQpjb3JyRnJhbWUgPSBkYXRhLmZyYW1lKGNvcihsYWJlbGVkX2ZpbmFsWywgMjozNl0pKQ0KY29ycnBsb3QoY29yKGxhYmVsZWRfZmluYWxbLCAyOjM2XSkpICNtYW55IG11bHRpY29sbGluZWFyIHZhcmlhYmxlcy4gRGVmaW5pbmUgY29sbGluZWFyaXR5IGFzIGNvcnJlbGF0aW9uID4uNA0KDQoNCkdMTV9EYXRhID0gZGF0YS5mcmFtZShsYWJlbGVkX2ZpbmFsJGtvaV9kaXNwb3NpdGlvbikNCg0KDQojYWxsIGVycjJzIGFyZSBjb2xsaW5lYXIgd2l0aCBlcnIxcy4gUmVtb3ZpbmcgZXJyMXMNCnZhcmlhYmxlX2NvdW50ZXIgPSAyDQp2YXJpYWJsZS5uYW1lcyA9IGNvbG5hbWVzKGxhYmVsZWRfZmluYWwpDQpmb3IgKGkgaW4gMjpsZW5ndGgobGFiZWxlZF9maW5hbCkpIHsNCiAgdmFyaWFibGUubmFtZXNbaV0NCiAgZXJyb3IxID0gZ3JlcGwoIl9lcnIxIiwgdmFyaWFibGUubmFtZXNbaV0sIGZpeGVkID0gVFJVRSkNCiAgaWYgKGVycm9yMSA9PSBGQUxTRSkgew0KICAgIEdMTV9EYXRhWywgdmFyaWFibGVfY291bnRlcl0gPSBsYWJlbGVkX2ZpbmFsWywgaV0NCiAgICB2YXJpYWJsZV9jb3VudGVyID0gdmFyaWFibGVfY291bnRlciArIDENCiAgfSBlbHNlew0KICAgIHZhcmlhYmxlLm5hbWVzW2ldID0gTkENCiAgfQ0KfQ0KdmFyaWFibGUubmFtZXMgPSBuYS5vbWl0KHZhcmlhYmxlLm5hbWVzKQ0KY29sbmFtZXMoR0xNX0RhdGEpID0gdmFyaWFibGUubmFtZXMNCkdMTV9EYXRhDQoNCmNvcnJGcmFtZTIgPSBkYXRhLmZyYW1lKGNvcihHTE1fRGF0YVssIDI6ZGltKEdMTV9EYXRhKVsyXV0pKQ0KY29ycnBsb3QoY29yKEdMTV9EYXRhWywgMjpkaW0oR0xNX0RhdGEpWzJdXSkpICNNYW55IGNvcnJlbGF0ZWQgdmFyaWFibGVzIHJlbWFpbg0KDQoNCiNLT0lfcGVyaW9kIGlzIGNvbGxpbmVhciB3aXRoIGtvaV90aW1lMGINCkdMTV9EYXRhID0gc3Vic2V0KEdMTV9EYXRhLCBzZWxlY3QgPSAtYyhrb2lfdGltZTBiaykpDQoja29pX3BlcmlvZF9lcnIgaXMgY29sbGluZWFyIHdpdGgga29pX3RpbWUwYmsgZXJyb3INCkdMTV9EYXRhID0gc3Vic2V0KEdMTV9EYXRhLCBzZWxlY3QgPSAtYyhrb2lfdGltZTBia19lcnIyKSkNCiNLb2lfcGVyaW9kIGlzIGNvbGxpbmVhciB3aXRoIGtvaV9wZXJpb2RfZXJyMg0KR0xNX0RhdGEgPSBzdWJzZXQoR0xNX0RhdGEsIHNlbGVjdCA9IC1jKGtvaV9wZXJpb2RfZXJyMikpDQoja29pX2ltcGFjdCBpcyBjb2xsaW5lYXIgd2l0aCBrb2lfaW1wYWN0X2VycjINCkdMTV9EYXRhID0gc3Vic2V0KEdMTV9EYXRhLCBzZWxlY3QgPSAtYyhrb2lfaW1wYWN0X2VycjIpKQ0KI2tvaV9kZXB0aCBpcyBjb2xsaW5lYXIgd2l0aCBrb2lfbW9kZWxfc25yDQpHTE1fRGF0YSA9IHN1YnNldChHTE1fRGF0YSwgc2VsZWN0ID0gLWMoa29pX21vZGVsX3NucikpDQoja29pIGltcGFjdCBpcyBjb2xsaW5lYXIgd2l0aCBrb2lfcHJhZCBhbmQga29pX3ByYWRfZXJyDQpHTE1fRGF0YSA9IHN1YnNldChHTE1fRGF0YSwgc2VsZWN0ID0gLWMoa29pX3ByYWQsIGtvaV9wcmFkX2VycjIpKQ0KI2tvaV90ZXEgaXMgY29sbGluZWFyIHdpdGggS09JX2luc29sLCBLb2lfaW5zb2xfZXJyLCBrb2lfc2xvZ2csIGtvaV9zcmFkLCBhbmQga29pX3NyYWRfZXJyDQpHTE1fRGF0YSA9IHN1YnNldChHTE1fRGF0YSwNCiAgICAgICAgICAgICAgICAgIHNlbGVjdCA9IC1jKGtvaV9pbnNvbCwga29pX2luc29sX2VycjIsIGtvaV9zbG9nZywga29pX3NyYWQsIGtvaV9zcmFkX2VycjIpKQ0KI2tvaV9zdGVmZiBpcyBjb2xsaW5lYXIgd2l0aCBrb2lfc3RlZmZfZXJyMiBhbmQga29pX3Nsb2dnX2VycjINCkdMTV9EYXRhID0gc3Vic2V0KEdMTV9EYXRhLCBzZWxlY3QgPSAtYyhrb2lfc2xvZ2dfZXJyMiwga29pX3N0ZWZmX2VycjIpKQ0KY29ycnBsb3QoY29yKEdMTV9EYXRhWywgMjpkaW0oR0xNX0RhdGEpWzJdXSkpDQojQWxsIG1ham9yIG11bHRpY29sbGluZWFyaXR5IGhhcyBub3cgYmVlbiByZW1vdmVkDQoNCg0KYGBgDQpTaW1pbGFybHkgdG8gWEdCb29zdCwgdGhlIGN1dG9mZiB0aHJlc2hvbGQgIGZvciBwcmVkaWN0aW9uIG11c3QgYmUgdHVuZWQuIE91ciBncm91cCBkZWNpZGVkIHRvIHJ1biBtdWx0aXBsZSB0ZXN0cyByYW5naW5nIGZyb20gMC4xIHRvIDEgdG8gZGV0ZXJtaW5lIHRoYXQgdGhlIGlkZWFsIHRocmVzaG9sZCB2YWx1ZSBvZiAwLjUgc2hvdWxkIGJlIHVzZWQgYXMgdGhhdCBjb3JyZXNwb25kZWQgd2l0aCB0aGUgbG93ZXN0IGF2ZXJhZ2UgZXJyb3IuIA0KYGBge3J9DQp0aHJlc2hvbGQgPSAwLjENCkdMTV9lcnJvciA9IHJlcCgwLCA1KQ0KR0xNX2Vycm9yX3RocmVzaG9sZHMgPSByZXAoMCwgMTApDQppbmRleCA9IDENCndoaWxlICh0aHJlc2hvbGQgPCAxKSB7ICNsb29wIGZvciB0aHJlc2hvbGQgYW5hbHlzaXMNCiAgR0xNX2Vycm9yID0gcmVwKDAsIDUpIA0KICBmb3IgKGZvbGQgaW4gMTo1KSB7I0stY3Jvc3MgZm9sZCB2YWxpZGF0aW9uDQoNCiAgICAjdHJhaW5pbmcvdGVzdGluZyBzZXQgICAgDQogICAgc2V0LnNlZWQoZm9sZCkNCiAgICBudW1fc2FtcGxlcyA9IGRpbShHTE1fRGF0YSlbMV0NCiAgICBzYW1wbGluZy5yYXRlID0gMC44DQogICAgdHJhaW5pbmcgPSBzYW1wbGUoMTpudW1fc2FtcGxlcywgc2FtcGxpbmcucmF0ZSAqIG51bV9zYW1wbGVzLCByZXBsYWNlID0gRkFMU0UpDQogICAgdHJhaW5pbmdTZXQgPSBzdWJzZXQoR0xNX0RhdGFbdHJhaW5pbmcsIF0pDQogICAgdGVzdGluZyA9IHNldGRpZmYoMTpudW1fc2FtcGxlcywgdHJhaW5pbmcpDQogICAgdGVzdGluZ1NldCA9IHN1YnNldChHTE1fRGF0YVt0ZXN0aW5nLF0pDQogICAgZGVmYXVsdFcgPSBnZXRPcHRpb24oIndhcm4iKQ0KICAgIG9wdGlvbnMod2FybiA9IC0xKQ0KICAgICNzZXQgdXAgbW9kZWwNCiAgICBMb2dpc3RpY1JlZyA9IGdsbShrb2lfZGlzcG9zaXRpb24gfiAuLA0KICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbmluZ1NldCwNCiAgICAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSBiaW5vbWlhbChsb2dpdCkpDQogICAgDQogICAgDQogICAgb3B0aW9ucyh3YXJuID0gZGVmYXVsdFcpDQogICAgcHJlZGljdGlvbnMgPSBwcmVkaWN0KExvZ2lzdGljUmVnLCB0ZXN0aW5nU2V0LCB0eXBlID0gInJlc3BvbnNlIikNCiAgICBwcmVkaWN0ZWRMYWJlbHMgPSByZXAoMCwgc2l6ZVRlc3RTZXQpDQogICAgcHJlZGljdGVkTGFiZWxzID0gaWZlbHNlKHByZWRpY3Rpb25zID4gdGhyZXNob2xkLCAnRkFMU0UgUE9TSVRJVkUnLCAnQ09ORklSTUVEJykgI3RoaXMgcGFyYW1ldGVyIGlzIHR1bmVkDQogICAgDQogICAgDQogICAgI2RldGVybWluZSBlcnJvcg0KICAgIGVycm9yID0gc3VtKHByZWRpY3RlZExhYmVscyAhPSB0ZXN0aW5nU2V0JGtvaV9kaXNwb3NpdGlvbikNCiAgICBtaXNjbGFzc2lmaWNhdGlvblJhdGVMUiA9IGVycm9yIC8gc2l6ZVRlc3RTZXQNCiAgICBHTE1fZXJyb3JbZm9sZF0gPSBtaXNjbGFzc2lmaWNhdGlvblJhdGVMUg0KICB9DQogIA0KICBHTE1fZXJyb3JfdGhyZXNob2xkc1tpbmRleF0gPSBtZWFuKEdMTV9lcnJvcikNCiAgaW5kZXggPSBpbmRleCArIDENCiAgdGhyZXNob2xkID0gdGhyZXNob2xkICsgLjENCn0NCm9yZGVyKEdMTV9lcnJvcl90aHJlc2hvbGRzKSAjTG93ZXN0IGVycm9yIGlzIHRocmVzaG9sZCA9IC41DQpBdmdFcnJvckdMTSA9IEdMTV9lcnJvcl90aHJlc2hvbGRzW29yZGVyKEdMTV9lcnJvcl90aHJlc2hvbGRzKVsxXV0NCnBhc3RlKCJMb2dpc3RpYyBSZWdyZXNzaW9uIEVycm9yOiAiLCByb3VuZChBdmdFcnJvckdMTSoxMDAsMiksIiUiLHNlcD0iIikNCnBsb3QoDQogIHggPSAxOjEwIC8gMTAsDQogIHkgPSBHTE1fZXJyb3JfdGhyZXNob2xkcywNCiAgbWFpbiA9ICJBdmcgRXJyb3Igb3ZlciBkaWZmZXJlbnQgdGhyZXNob2xkcyBmb3IgTG9naXN0aWMgUmVncmVzc2lvbiBNb2RlbCIsDQogIHhsYWIgPSAiQ3V0b2ZmIFRocmVzaG9sZCIsDQogIHlsYWIgPSAiTWlzY2xhc3NpZmljYXRpb24gUmF0ZSINCikNCmxpbmVzKHggPSAxOjEwIC8gMTAsIHkgPSBHTE1fZXJyb3JfdGhyZXNob2xkcykNCmBgYA0KTG9naXN0aWMgUmVncmVzc2lvbiBoYXMgYW4gZXJyb3IgcmF0ZSBvZiAxMS43JSwgbWFraW5nIGl0IHRoZSB3b3JzdCBtb2RlbCB5ZXQuIEl0cyBlcnJvciBpcyBiZXN0IHdoZW4gdGhyZXNob2xkID0gLjUuDQoNCiMjIDMuMikgTW9kZWxzIHVzaW5nIHNjYWxlZCBkYXRhOg0KDQoNClRoZSBtb2RlbHMgYmVsb3cgYWxsIHJlcXVpcmUgbm9ybWFsaXphdGlvbiBvZiB0aGUgZGF0YSB0byBiZSAgZWZmZWN0aXZlLiBUaGlzIGlzIGFuIGltcG9ydGFudCBzdGVwIGFzIGFsbCBmZWF0dXJlcyBuZWVkIHRvIGJlIGluIHRoZSBzYW1lIHNjYWxlLiBJZiBub3QsIHRoZSBmZWF0dXJlcyB3aXRoIGxhcmdlciBzY2FsZXMgd291bGQgZG9taW5hdGUgdGhlIG1vZGVsIGNhdXNpbmcgaXQgdG8gYmUgaW5hY2N1cmF0ZS4gVG8gZG8gdGhpcywgd2UgdXNlZCB0aGUg4oCcc2NhbGXigJ0gZnVuY3Rpb24gdG8gbm9ybWFsaXplIGFsbCB0aGUgZGVwZW5kZW50IHZhcmlhYmxlcy4gV2UgYWxzbyBjaGFuZ2VkIHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZSwga29pX2Rpc3Bvc2l0aW9uLCB0byBiZSBiaW5hcnkNCg0KIyMjIDMuMi4yKSBOZXVyYWwgTmV0d29yazogT3JpZ2luYWwgVmFyaWFibGVzDQoNClRoZSBOZXVyYWwgTmV0d29yayBtb2RlbCBpcyBidWlsdCB0aHJvdWdoIGZ1bmN0aW9ucywg4oCcbmV1cm9uc+KAnSB0aGF0IGFyZSB0aGVuIG9yZ2FuaXplZCBpbnRvIGxheWVycy4gSXQgaXMgYW4gYWR2YW5jZWQgbW9kZWwgdGhhdCBpcyBpZGVhbGx5IHN1aXRlZCBmb3IgY29tcGxleCBwcm9ibGVtcyBhcyBpdCByZXF1aXJlcyBzaWduaWZpY2FudCBjb21wdXRhdGlvbmFsIHJlc291cmNlcy4gSW4gYWRkaXRpb24sIGl0IGlzIHF1aXRlIGRpZmZpY3VsdCB0byB1bmRlcnN0YW5kIGFmdGVyd2FyZHMgZ2l2ZW4gdGhlIGNvbXBsZXhpdHkgb2YgdGhlIG1hdGggd2l0aGluIHRoZSBtb2RlbC4gT3VyIGdyb3VwIHdhcyBhYmxlIHRvIHNlZSB0aGUgc2lnbmlmaWNhbnQgdXNlIG9mIGNvbXB1dGF0aW9uYWwgcmVzb3VyY2VzIGFzIG91ciBjb21wdXRlciB3YXMgdW5hYmxlIHRvIHJ1biB0aGUgbW9kZWwuIEZvciB0aGlzIHJlYXNvbiwgdGhlIGdyb3VwIGRpZCBub3QgdXNlIEstZm9sZCBjcm9zcy12YWxpZGF0aW9uIGluIG9yZGVyIHRvIGxvd2VyIHRoZSBjb21wdXRhdGlvbmFsIHBvd2VyIHJlcXVpcmVkIHRvIHJ1biB0aGUgbW9kZWwsIGJ1dCBpbiByZWFsLWxpZmUgYXBwbGljYXRpb24gSy1mb2xkIGNyb3NzLXZhbGlkYXRpb24gc2hvdWxkIHN0aWxsIGJlIGRvbmUuIA0KDQpXaGVuIGNob29zaW5nIHRoZSBudW1iZXIgb2YgbmV1cm9ucyBhbmQgaGlkZGVuIGxheWVycyBpdCBpcyBpbXBvcnRhbnQgdG8gZmluZCB0aGUgcmlnaHQgYmFsYW5jZSBiZXR3ZWVuIGFjY3VyYWN5IGFuZCBvdmVyLWZpdHRpbmcuIE91ciBncm91cCBtYW51YWxseSBhZGp1c3RlZCB0aGUgbnVtYmVyIG9mIG5ldXJvbnMgYW5kIGhpZGRlbiBsYXllcnMsIHRlc3RpbmcgdmFyaWF0aW9ucyBzdWNoIGFzIDQmMiwgNSYxLCA2LCAzJjEsIGV0YywgdW50aWwgd2UgZm91bmQgdGhhdCB0d28gbmV1cm9ucyBhbmQgb25lIGhpZGRlbiBsYXllciBhbGxvd2VkIHRoZSBtb2RlbCB0byBjb252ZXJnZS4gVGhlIG5leHQgc3RlcCBpbiB0aGUgbW9kZWwgd2FzIHRvIGNob29zZSB0aGUgdGhyZXNob2xkIHZhbHVlIGZvciBjbGFzc2lmaWNhdGlvbi4gVGhpcyB3YXMgc2ltaWxhciB0byBjaG9vc2luZyB0aGUgdGhyZXNob2xkIGFzIHdlIGRpZCBpbiBMb2dpc3RpYyBSZWdyZXNzaW9uLiBUaGUgaWRlYWwgdGhyZXNob2xkIG9mIDAuNSB3YXMgZm91bmQgYnkgcGxvdHRpbmcgdGhlIGF2ZXJhZ2UgZXJyb3IgZm9yIGVhY2ggdGhyZXNob2xkIHJhbmdpbmcgZnJvbSAwLjEgdG8gMSBhcyB0aGF0IGNvcnJlc3BvbmRlZCB3aXRoIHRoZSBsb3dlc3QgbWlzY2xhc3NpZmljYXRpb24gcmF0ZS4NCg0KDQpgYGB7cn0NCiNDcmVhdGUgdGVzdGluZyBhbmQgdHJhaW5pbmcgc2V0DQpzZXQuc2VlZCgxMjMpDQpzY2FsZWRfZGF0YSA9IGRhdGEuZnJhbWUoc2NhbGUobGFiZWxlZF9maW5hbFssIDI6MzZdKSkgI25vcm1hbGl6ZSBkYXRhDQpzY2FsZWRfZGF0YSRrb2lfZGlzcG9zaXRpb24gPSBpZmVsc2UobGFiZWxlZF9maW5hbCRrb2lfZGlzcG9zaXRpb24gPT0gIkNPTkZJUk1FRCIsIDEsIDApDQpudW1fc2FtcGxlcyA9IGRpbShzY2FsZWRfZGF0YSlbMV0NCnNhbXBsaW5nLnJhdGUgPSAwLjgNCnRyYWluaW5nID0gc2FtcGxlKDE6bnVtX3NhbXBsZXMsIHNhbXBsaW5nLnJhdGUgKiBudW1fc2FtcGxlcywgcmVwbGFjZSA9IEZBTFNFKQ0KdHJhaW5pbmdTZXQubm9ybSA9IHN1YnNldChzY2FsZWRfZGF0YVt0cmFpbmluZyxdKQ0KdGVzdGluZyA9IHNldGRpZmYoMTpudW1fc2FtcGxlcywgdHJhaW5pbmcpDQp0ZXN0aW5nU2V0Lm5vcm0gPSBzdWJzZXQoc2NhbGVkX2RhdGFbdGVzdGluZywgXSkNCnNpemVUZXN0U2V0ID0gZGltKHRlc3RpbmdTZXQubm9ybSlbMV0NCg0KI3NldCB1cCB2YXJpYWJsZXMgZm9yIG5ldXJhbCBuZXR3b3JrLiBTaW5jZSAzNiB2YXJpYWJsZXMgcHV0IHRvbyBtdWNoIGNvbXB1dGF0aW9uYWwgc3RyYWluLCBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gbmVlZGVkIHRvIHRha2UgcGxhY2UuIFdlIGNob3NlIHRvIHRha2Ugb25seSB0aGUgZmVhdHVyZXMsIG5vdCB0aGVpciBlcnJvcnMsIHRvIHJlZHVjZSBkaW1lbnNpb25hbGl0eS4NCg0Ka29pRFAubmFtZSA9ICJrb2lfZGlzcG9zaXRpb24iDQpudW1Db2xzID0gZGltKHRlc3RpbmdTZXQubm9ybSlbMl0NCnZhcmlhYmxlLm5hbWVzID0gY29sbmFtZXModGVzdGluZ1NldC5ub3JtKVsxOm51bUNvbHMgLSAxXQ0KdmFyaWFibGUubmFtZXMNCmZvciAoaSBpbiAxOmxlbmd0aCh2YXJpYWJsZS5uYW1lcykpIHsNCiAgZXJyb3IgPSBncmVwbCgiX2VyciIsIHZhcmlhYmxlLm5hbWVzW2ldLCBmaXhlZCA9IFRSVUUpDQogIGlmIChlcnJvcikgew0KICAgIHZhcmlhYmxlLm5hbWVzW2ldID0gTkENCiAgfQ0KICANCn0NCnZhcmlhYmxlLm5hbWVzID0gbmEub21pdCh2YXJpYWJsZS5uYW1lcykNCnZhcmlhYmxlLm5hbWVzID0gdmFyaWFibGUubmFtZXNbMToxNV0NCiN1c2UgZm9ybXVsYWljIGxpYnJhcnkgdG8gY3JlYXRlIGZvcm11bGENCm5uLmZvcm0gPC0NCiAgY3JlYXRlLmZvcm11bGEob3V0Y29tZS5uYW1lID0ga29pRFAubmFtZSwNCiAgICAgICAgICAgICAgICAgaW5wdXQubmFtZXMgPSB2YXJpYWJsZS5uYW1lcykNCm5uLmZvcm0NCg0KI0ZpdCBuZXVyYWwgbmV0d29yaw0Kbm5Nb2RlbDEgPSBuZXVyYWxuZXQoDQogIG5uLmZvcm0sDQogIGRhdGEgPSB0cmFpbmluZ1NldC5ub3JtLA0KICBoaWRkZW4gPSAyLA0KICANCiAgbGluZWFyLm91dHB1dCA9IEZBTFNFLA0KICBhY3QuZmN0ID0gImxvZ2lzdGljIg0KKQ0KcGxvdChubk1vZGVsMSkNCg0KI1ByZWRpY3QNCnByZWRpY3RlZExhYmVscyA9IGNvbXB1dGUobm5Nb2RlbDEsIHRlc3RpbmdTZXQubm9ybVssIHZhcmlhYmxlLm5hbWVzXSkNCg0KI3R1bmUgdGhyZXNob2xkIHBhcmFtZXRlcg0KdGhyZXNob2xkID0gLjENCk5ORXJyb3JzID0gcmVwKDAsIDEwKQ0KaW5kZXggPSAxDQp3aGlsZSAodGhyZXNob2xkIDw9IDEpIHsNCiAgcmVzdWx0cyA9IGRhdGEuZnJhbWUoYWN0dWFsID0gdGVzdGluZ1NldC5ub3JtJGtvaV9kaXNwb3NpdGlvbiwNCiAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdGlvbiA9IHByZWRpY3RlZExhYmVscyRuZXQucmVzdWx0KQ0KICByZXN1bHRzJHJvdW5kZWRQcmVkaWN0aW9uID0gaWZlbHNlKHJlc3VsdHMkcHJlZGljdGlvbiA+IHRocmVzaG9sZCwgMSwgMCkNCiAgZXJyb3IgPSBzdW0ocmVzdWx0cyRhY3R1YWwgIT0gcmVzdWx0cyRyb3VuZGVkUHJlZGljdGlvbikNCiAgTk5FcnJvcnNbaW5kZXhdID0gZXJyb3IgLyBzaXplVGVzdFNldA0KICB0aHJlc2hvbGQgPSB0aHJlc2hvbGQgKyAuMQ0KICBpbmRleCA9IGluZGV4ICsgMQ0KfQ0KDQpvcmRlcihOTkVycm9ycykgI2xvd2VzdCBtaXNjbGFzcyByYXRlIGlzIGF0IHRocmVzaG9sZCA9IC41DQpOZXVyYWxOZXRNaXNDbGFzc1JhdGUgPSBOTkVycm9yc1tvcmRlcihOTkVycm9ycylbMV1dDQpwYXN0ZSgiTmV1cmFsIE5ldCBNaXNjbGFzc2lmaWNhdGlvbiBSYXRlOiAiLHJvdW5kKDEwMCpOZXVyYWxOZXRNaXNDbGFzc1JhdGUsMiksIiUiLHNlcD0iIikNCnBsb3QoDQogIHggPSAxOjEwIC8gMTAsDQogIHkgPSBOTkVycm9ycywNCiAgbWFpbiA9ICJBdmcgRXJyb3Igb3ZlciBkaWZmZXJlbnQgdGhyZXNob2xkcyBmb3IgTmV1cmFsIE5ldHdvcmsiLA0KICB4bGFiID0gIkN1dG9mZiBUaHJlc2hvbGQiLA0KICB5bGFiID0gIk1pc2NsYXNzaWZpY2F0aW9uIFJhdGUiDQopDQpsaW5lcyh4ID0gMToxMCAvIDEwLCB5ID0gTk5FcnJvcnMpDQpgYGANCkluIHRoaXMgcnVuLCBOTiBoYWQgYW4gZXJyb3IgcmF0ZSBvZiBgciBOZXVyYWxOZXRNaXNDbGFzc1JhdGVgLCB3aXRoIGFuIG9wdGltYWwgY3V0b2ZmIHRocmVzaG9sZCBvZiAuNS4gV2Ugd2lsbCByZXZpc2l0IHRoZSBOTiBiZWxvdywgdG8gbG9vayBhdCB3YXlzIHRvIHVzZSBQQ0EgdG8gY2FwdHVyZSB2YXJpYXRpb24gd2hpbGUgcmVkdWNpbmcgbmVjZXNzYXJ5IGNvbXB1dGF0aW9uYWwgcmVzb3VyY2VzLiANCg0KIyMjIE5ldXJhbCBOZXR3b3JrOiBQQ0Egdy8xMyBWYXJpYWJsZXMNCg0KUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcyBhbGxvd3MgdXMgdG8gcmVkdWNlIGRpbWVuc2lvbmFsaXR5IHdoaWxlIGNhcHR1cmluZyBhcyBtdWNoIG9mIHRoZSB1bmRlcmx5aW5nIHZhcmlhdGlvbiBpbiB0aGUgZGF0YSBhcyBwb3NzaWJsZS4gVGhlIGZpcnN0IGFwcGxpY2F0aW9uIG9mIFBDQSB3ZSBmb3VuZCB3YXMgZm9yIE5ldXJhbCBOZXR3b3Jrcywgd2hpY2ggYXJlIHZlcnkgY29tcHV0YXRpb25hbGx5IGRpZmZpY3VsdC4gV2UgZGVjaWRlZCB0byBmaXJzdCB1c2UgYSBQQ0Egd2l0aCAxMyB2YXJpYWJsZXMsIHRoZSBzYW1lIG51bWJlciBvZiB2YXJpYWJsZXMgYXMgb3VyIG9yaWdpbmFsIG5ldXJhbCBuZXR3b3JrLiBUaGlzIHdvdWxkIG1lYW4gdGhlIHNhbWUgY29tcHV0YXRpb25hbCBzdHJhaW4sIGJ1dCB3aXRoIGZlYXR1cmVzIHRoYXQgYXJlIGd1YXJhbnRlZWQgdG8gY2FwdHVyZSBhcyBtdWNoIHZhcmlhdGlvbiBhcyBwb3NzaWJsZSB3aXRoIHRoYXQgbnVtYmVyIG9mIHZhcmlhYmxlcy4gDQogICANCmBgYHtyfQ0KcmVzLnBjYS5leG9wbGFuZXRzID0gcHJjb21wKGlkZW50aWZpZXJzX3JlbW92ZWRbMjozNl0sIGNlbnRlciA9IFRSVUUsIHNjYWxlID0gVFJVRSkgI3BlcmZvcm0gUENBDQpzdW1tYXJ5KHJlcy5wY2EuZXhvcGxhbmV0cykNClBvViA8LQ0KICByZXMucGNhLmV4b3BsYW5ldHMkc2RldiBeIDIgLyBzdW0ocmVzLnBjYS5leG9wbGFuZXRzJHNkZXYgXiAyKSAjZ2V0IHByb3BvcnRpb25zIG9mIHZhcmlhbmNlDQpudW1QY2FzID0gMTMNCnN1bShQb1ZbMTpudW1QY2FzXSkgI1RoaXMgZ2l2ZXMgdXMgODMlIGV4cGxhbmF0aW9uIG9mIHZhcmlhbmNlLiBUaGlzIGlzIHRoZSBudW1iZXIgb2YgdmFyaWFibGUgaW4gdGhlIG9yaWdpbmFsIG5ldXJhbCBuZXR3b3JrLCBzbyB3ZSBhcmUgdXNpbmcgdGhpcyB0byBnZXQgYSBiZXR0ZXIgTk4gd2l0aCB0aGUgc2FtZSBudW1iZXIgb2YgdmFyaWFibGVzDQpuZXdEYXRhU2V0ID0gZGF0YS5mcmFtZShyZXMucGNhLmV4b3BsYW5ldHMkeFssIDE6bnVtUGNhc10pDQpuZXdEYXRhU2V0JGxhYmVsID0gaWRlbnRpZmllcnNfcmVtb3ZlZCRrb2lfZGlzcG9zaXRpb24NCmZ2aXpfcGNhX3ZhcihyZXMucGNhLmV4b3BsYW5ldHMsIGNvbC52YXIgPSAiY29udHJpYiIpDQpjYW5kaWRhdGVzX1BDQSA9IG5ld0RhdGFTZXRbaWRlbnRpZmllcnNfcmVtb3ZlZCRrb2lfZGlzcG9zaXRpb24gPT0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDQU5ESURBVEUiLCBdICNzZXBhcmF0ZSBvdXQganVzdCB0aGUgY2FuZGlkYXRlcw0KbGFiZWxlZF9QQ0EgPSBuZXdEYXRhU2V0W2lkZW50aWZpZXJzX3JlbW92ZWQka29pX2Rpc3Bvc2l0aW9uICE9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAiQ0FORElEQVRFIiwgXQ0KbGFiZWxlZF9QQ0EgPSBkcm9wbGV2ZWxzKGxhYmVsZWRfUENBKQ0KY2FuZGlkYXRlc19QQ0EgPSBkcm9wbGV2ZWxzKGNhbmRpZGF0ZXNfUENBKQ0KYGBgDQpSZXJ1bm5pbmcgTmV1cmFsIE5ldHdvcmsgTW9kZWwNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0KTk5fbGFiZWxlZF9QQ0EgPSBsYWJlbGVkX1BDQVssIDE6bnVtUGNhc10NCk5OX2xhYmVsZWRfUENBJGtvaV9kaXNwb3NpdGlvbiA9IGlmZWxzZShsYWJlbGVkX1BDQSRsYWJlbCA9PSAiQ09ORklSTUVEIiwgMSwgMCkNCnN1bW1hcnkoc2NhbGVkX2RhdGEpDQpoZWFkKHNjYWxlZF9kYXRhKQ0KbnVtX3NhbXBsZXMgPSBkaW0oc2NhbGVkX2RhdGEpWzFdDQpzYW1wbGluZy5yYXRlID0gMC44DQoNCiNjcmVhdGUgdHJhaW5pbmcvdGVzdGluZyBzZXRzDQp0cmFpbmluZyA9IHNhbXBsZSgxOm51bV9zYW1wbGVzLCBzYW1wbGluZy5yYXRlICogbnVtX3NhbXBsZXMsIHJlcGxhY2UgPSBGQUxTRSkNCnRyYWluaW5nU2V0Lm5vcm0uUENBID0gc3Vic2V0KE5OX2xhYmVsZWRfUENBW3RyYWluaW5nLF0pDQp0ZXN0aW5nID0gc2V0ZGlmZigxOm51bV9zYW1wbGVzLCB0cmFpbmluZykNCnRlc3RpbmdTZXQubm9ybS5QQ0EgPSBzdWJzZXQoTk5fbGFiZWxlZF9QQ0FbdGVzdGluZywgXSkNCnNpemVUZXN0U2V0ID0gZGltKHRlc3RpbmdTZXQubm9ybSlbMV0NCmxhYmVsLm5hbWUgPSAibGFiZWwiDQp2YXJpYWJsZS5uYW1lcyA9IHJlcCgwLCBudW1QY2FzKQ0KbnVtQ29scyA9IGRpbSh0ZXN0aW5nU2V0Lm5vcm0uUENBKVsyXQ0KdmFyaWFibGUubmFtZXMgPSBjb2xuYW1lcyh0ZXN0aW5nU2V0Lm5vcm0uUENBKVsxOm51bUNvbHMgLSAxXQ0KdmFyaWFibGUubmFtZXMNCm5uLmZvcm0gPC0NCiAgY3JlYXRlLmZvcm11bGEob3V0Y29tZS5uYW1lID0ga29pRFAubmFtZSwNCiAgICAgICAgICAgICAgICAgaW5wdXQubmFtZXMgPSB2YXJpYWJsZS5uYW1lcykNCm5uLmZvcm0NCg0KI3JlcnVuIG5ldXJhbCBuZXR3b3JrDQpubk1vZGVsMiA9IG5ldXJhbG5ldCgNCiAgbm4uZm9ybSwNCiAgZGF0YSA9IHRyYWluaW5nU2V0Lm5vcm0uUENBLA0KICBoaWRkZW4gPSAyLA0KICBsaW5lYXIub3V0cHV0ID0gRkFMU0UsDQogIGFjdC5mY3QgPSAibG9naXN0aWMiDQopDQpwbG90KG5uTW9kZWwyKQ0KI0ZpbmQgcmVzdWx0cw0KcHJlZGljdGVkTGFiZWxzID0gY29tcHV0ZShubk1vZGVsMiwgdGVzdGluZ1NldC5ub3JtLlBDQVssIHZhcmlhYmxlLm5hbWVzXSkNCg0KI1R1bmUgdGhyZXNob2xkDQp0aHJlc2hvbGQgPSAuMQ0KTk5FcnJvcnMgPSByZXAoMCwgMTApDQppbmRleCA9IDENCndoaWxlICh0aHJlc2hvbGQgPD0gMSkgew0KICByZXN1bHRzID0gZGF0YS5mcmFtZShhY3R1YWwgPSB0ZXN0aW5nU2V0Lm5vcm0uUENBJGtvaV9kaXNwb3NpdGlvbiwNCiAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdGlvbiA9IHByZWRpY3RlZExhYmVscyRuZXQucmVzdWx0KQ0KICByZXN1bHRzJHJvdW5kZWRQcmVkaWN0aW9uID0gaWZlbHNlKHJlc3VsdHMkcHJlZGljdGlvbiA+IHRocmVzaG9sZCwgMSwgMCkNCiAgZXJyb3IgPSBzdW0ocmVzdWx0cyRhY3R1YWwgIT0gcmVzdWx0cyRyb3VuZGVkUHJlZGljdGlvbikNCiAgTk5FcnJvcnNbaW5kZXhdID0gZXJyb3IgLyBzaXplVGVzdFNldA0KICB0aHJlc2hvbGQgPSB0aHJlc2hvbGQgKyAuMQ0KICBpbmRleCA9IGluZGV4ICsgMQ0KfQ0Kb3JkZXIoTk5FcnJvcnMpICNsb3dlc3QgbWlzY2xhc3NpZmljYXRpb24gcmF0ZSBpcyBhdCB0aHJlc2hvbGQgPSAuNQ0KUENBXzEzX3ZfTmV1cmFsTmV0TWlzQ2xhc3NSYXRlID0gTk5FcnJvcnNbb3JkZXIoTk5FcnJvcnMpWzFdXQ0KcGFzdGUoIk5ldXJhbCBOZXQgKFBDQSwgMTMgdmFyKSBNaXNjbGFzc2lmaWNhdGlvbiBSYXRlOiAiLHJvdW5kKDEwMCpQQ0FfMTNfdl9OZXVyYWxOZXRNaXNDbGFzc1JhdGUsMiksIiUiLHNlcD0iIikNCg0KcGxvdCgNCiAgeCA9IDE6MTAgLyAxMCwNCiAgeSA9IE5ORXJyb3JzLA0KICBtYWluID0gIkF2ZyBFcnJvciBvdmVyIHRocmVzaG9sZHMgZm9yIFBDQSBOZXVyYWwgTmV0d29yayB3LzEzIFZhcnMiLA0KICB4bGFiID0gIkN1dG9mZiBUaHJlc2hvbGQiLA0KICB5bGFiID0gIk1pc2NsYXNzaWZpY2F0aW9uIFJhdGUiDQopDQpsaW5lcyh4ID0gMToxMCAvIDEwLCB5ID0gTk5FcnJvcnMpDQpgYGANCldlIGdldCBhYmV0dGVyIG1pc2NsYXNzaWZpY2F0aW9uIHJhdGUgZnJvbSB0aGUgUENBIHZlcnNpb24gb2YgTk4gdXNpbmcgMTMgdmFyaWFibGVzIG9mIGByIHJvdW5kKFBDQV8xM192X05ldXJhbE5ldE1pc0NsYXNzUmF0ZSoxMDAsMilgJSwgd2hpY2ggaXMgYHIgcm91bmQoMTAwKihOZXVyYWxOZXRNaXNDbGFzc1JhdGUgLSBQQ0FfMTNfdl9OZXVyYWxOZXRNaXNDbGFzc1JhdGUpLDIpYCUgbGVzcyB0aGFuIG91ciBsYXN0IE5ldXJhbCBOZXR3b3JrLCBhbmQgYW4gb3B0aW1hbCBjdXRvZmYgdGhyZXNob2xkIG9mIC41LiANCg0KIyMjIDMuMi4zKSBOZXVyYWwgTmV0d29yazogUENBIHcvMjAgVmFyaWFibGVzDQoNCkxhc3RseSAoZm9yIE5OcyksIHdlIGRlY2lkZWQgdG8gc2VlIGhvdyBtYW55IHZhcmlhYmxlcyBjb3VsZCBjYXB0dXJlIDk1JSBvZiB2YXJpYXRpb24uIFdlIGZvdW5kIHRoYXQgMjAgdmFyaWFibGVzIHdhcyBzdWZmaWNpZW50OyB0aGlzIG1lYW5zIHRoYXQgMTYgb2Ygb3VyIDM2IHZhcmlhYmxlcywgb3IgYHIgcm91bmQoMTYvMzYqMTAwLDIpYCUgcmVwcmVzZW50IG9ubHkgNSUgb2YgdmFyaWF0aW9uLiANCmBgYHtyfQ0KI0xhc3QgTmV1cmFsIE5ldHdvcmssIFBDQSwgd2l0aCA5NSUgb2YgdmFyaWF0aW9uIGV4cGxhaW5lZA0KbnVtVmFycyA9IChkaW0oKGxhYmVsZWRfZmluYWwpKVsyXSkNCmZvciAoaSBpbiAxOihudW1WYXJzKSkgew0KICBpZiAoc3VtKFBvVlsxOmldKSA+PSAuOTUpIHsNCiAgICBudW1QY2FzID0gaQ0KICAgIGJyZWFrDQogICAgDQogIH0NCn0NCg0Kc3VtKFBvVlsxOm51bVBjYXNdKQ0KbmV3RGF0YVNldCA9IGRhdGEuZnJhbWUocmVzLnBjYS5leG9wbGFuZXRzJHhbLCAxOm51bVBjYXNdKQ0KDQpuZXdEYXRhU2V0JGxhYmVsID0gaWRlbnRpZmllcnNfcmVtb3ZlZCRrb2lfZGlzcG9zaXRpb24NCg0KY2FuZGlkYXRlc19QQ0EgPSBuZXdEYXRhU2V0W2lkZW50aWZpZXJzX3JlbW92ZWQka29pX2Rpc3Bvc2l0aW9uID09DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ0FORElEQVRFIixdICNzZXBhcmF0ZSBvdXQganVzdCB0aGUgY2FuZGlkYXRlcw0KbGFiZWxlZF9QQ0EgPSBuZXdEYXRhU2V0W2lkZW50aWZpZXJzX3JlbW92ZWQka29pX2Rpc3Bvc2l0aW9uICE9DQogICAgICAgICAgICAgICAgICAgICAgICAgICAiQ0FORElEQVRFIixdDQpsYWJlbGVkX1BDQSA9IGRyb3BsZXZlbHMobGFiZWxlZF9QQ0EpDQpjYW5kaWRhdGVzX1BDQSA9IGRyb3BsZXZlbHMoY2FuZGlkYXRlc19QQ0EpDQpgYGANCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpOTl9sYWJlbGVkX1BDQSA9IGxhYmVsZWRfUENBWywgMTpudW1QY2FzXQ0KTk5fbGFiZWxlZF9QQ0Eka29pX2Rpc3Bvc2l0aW9uID0gaWZlbHNlKGxhYmVsZWRfUENBJGxhYmVsID09ICJDT05GSVJNRUQiLCAxLCAwKQ0KaGVhZChzY2FsZWRfZGF0YSkNCiN0ZXN0aW5nIGFuZCB0cmFpbmluZyBzZXRzDQpudW1fc2FtcGxlcyA9IGRpbShzY2FsZWRfZGF0YSlbMV0NCnNhbXBsaW5nLnJhdGUgPSAwLjgNCnRyYWluaW5nID0gc2FtcGxlKDE6bnVtX3NhbXBsZXMsIHNhbXBsaW5nLnJhdGUgKiBudW1fc2FtcGxlcywgcmVwbGFjZSA9IEZBTFNFKQ0KdHJhaW5pbmdTZXQubm9ybS5QQ0EgPSBzdWJzZXQoTk5fbGFiZWxlZF9QQ0FbdHJhaW5pbmcsXSkNCnRlc3RpbmcgPSBzZXRkaWZmKDE6bnVtX3NhbXBsZXMsIHRyYWluaW5nKQ0KdGVzdGluZ1NldC5ub3JtLlBDQSA9IHN1YnNldChOTl9sYWJlbGVkX1BDQVt0ZXN0aW5nLCBdKQ0Kc2l6ZVRlc3RTZXQgPSBkaW0odGVzdGluZ1NldC5ub3JtKVsxXQ0KbGFiZWwubmFtZSA9ICJsYWJlbCINCnZhcmlhYmxlLm5hbWVzID0gcmVwKDAsIG51bVBjYXMpDQoNCm51bUNvbHMgPSBkaW0odGVzdGluZ1NldC5ub3JtLlBDQSlbMl0NCnZhcmlhYmxlLm5hbWVzID0gY29sbmFtZXModGVzdGluZ1NldC5ub3JtLlBDQSlbMTpudW1Db2xzIC0gMV0NCg0Kbm4uZm9ybSA8LQ0KICBjcmVhdGUuZm9ybXVsYShvdXRjb21lLm5hbWUgPSBrb2lEUC5uYW1lLA0KICAgICAgICAgICAgICAgICBpbnB1dC5uYW1lcyA9IHZhcmlhYmxlLm5hbWVzKQ0Kbm4uZm9ybQ0KDQpubk1vZGVsMyA9IG5ldXJhbG5ldCgNCiAgbm4uZm9ybSwNCiAgZGF0YSA9IHRyYWluaW5nU2V0Lm5vcm0uUENBLA0KICBoaWRkZW4gPSAyLA0KICBsaW5lYXIub3V0cHV0ID0gRkFMU0UsDQogIGFjdC5mY3QgPSAibG9naXN0aWMiDQopDQoNCnBsb3Qobm5Nb2RlbDMpDQoNCiNNYWtlIHByZWRpY3Rpb24NCnByZWRpY3RlZExhYmVscyA9IGNvbXB1dGUobm5Nb2RlbDMsIHRlc3RpbmdTZXQubm9ybS5QQ0FbLCB2YXJpYWJsZS5uYW1lc10pDQoNCmluZGV4ID0gMQ0KdGhyZXNob2xkID0gLjENCk5ORXJyb3JzID0gcmVwKDAsIDEwKQ0Kd2hpbGUgKHRocmVzaG9sZCA8PSAxKSB7ICN0dW5lIHRocmVzaG9sZCBwYXJhbWV0ZXINCiAgcmVzdWx0cyA9IGRhdGEuZnJhbWUoYWN0dWFsID0gdGVzdGluZ1NldC5ub3JtLlBDQSRrb2lfZGlzcG9zaXRpb24sDQogICAgICAgICAgICAgICAgICAgICAgIHByZWRpY3Rpb24gPSBwcmVkaWN0ZWRMYWJlbHMkbmV0LnJlc3VsdCkNCiAgcmVzdWx0cyRyb3VuZGVkUHJlZGljdGlvbiA9IGlmZWxzZShyZXN1bHRzJHByZWRpY3Rpb24gPiB0aHJlc2hvbGQsIDEsIDApDQogIGVycm9yID0gc3VtKHJlc3VsdHMkYWN0dWFsICE9IHJlc3VsdHMkcm91bmRlZFByZWRpY3Rpb24pDQogIE5ORXJyb3JzW2luZGV4XSA9IGVycm9yIC8gc2l6ZVRlc3RTZXQNCiAgdGhyZXNob2xkID0gdGhyZXNob2xkICsgLjENCiAgaW5kZXggPSBpbmRleCArIDENCn0NCk5ORXJyb3JzICNsb3dlc3QgZXJyb3IgaXMgd2l0aCB0aHJlc2hvbGQgPSAuNg0KUENBXzIwX3ZfTmV1cmFsTmV0TWlzQ2xhc3NSYXRlID0gTk5FcnJvcnNbb3JkZXIoTk5FcnJvcnMpWzFdXQ0KcGxvdCgNCiAgeCA9IDE6MTAgLyAxMCwNCiAgeSA9IE5ORXJyb3JzLA0KICBtYWluID0gIkF2ZyBFcnJvciBvdmVyIHRocmVzaG9sZHMgZm9yIFBDQSBOZXVyYWwgTmV0d29yayB3LzIwIFZhcnMiLA0KICB4bGFiID0gIkN1dG9mZiBUaHJlc2hvbGQiLA0KICB5bGFiID0gIk1pc2NsYXNzaWZpY2F0aW9uIFJhdGUiDQopDQpsaW5lcyh4ID0gMToxMCAvIDEwLCB5ID0gTk5FcnJvcnMpDQpgYGANCkFzIGV4cGVjdGVkLCB0aGlzIHZlcnNpb24gaGFzIHRoZSBsb3dlc3QgbWlzY2xhc3NpZmljYXRpb24gcmF0ZSBvZiBgciByb3VuZChQQ0FfMjBfdl9OZXVyYWxOZXRNaXNDbGFzc1JhdGUqMTAwLDIpYCUsIHdoaWNoIGlzIGByIHJvdW5kKChQQ0FfMTNfdl9OZXVyYWxOZXRNaXNDbGFzc1JhdGUtUENBXzIwX3ZfTmV1cmFsTmV0TWlzQ2xhc3NSYXRlKSoxMDAsMilgJSBsb3dlciB0aGFuIHRoZSAxMyB2YXJpYWJsZSBQQ0EgTk4sIGFuZCBgciByb3VuZCgoTmV1cmFsTmV0TWlzQ2xhc3NSYXRlLVBDQV8yMF92X05ldXJhbE5ldE1pc0NsYXNzUmF0ZSkqMTAwLDIpYCUgbG93ZXIgdGhhbiB0aGUgb3JpZ2luYWwgbmV1cmFsIG5ldHdvcmsuIEFzIHlvdSBjYW4gc2VlLCBob3dldmVyLCB3ZSBhcmUgcmVhY2hpbmcgYSBwb2ludCBvZiBkaW1pbmlzaGluZyByZXR1cm5zOyBhZGRpbmcgfjE1JSBtb3JlIHZhcmlhdGlvbiBvbmx5IGRlY3JlYXNlZCB0aGUgZXJyb3IgcmF0ZSBieSANCmByIHJvdW5kKChQQ0FfMTNfdl9OZXVyYWxOZXRNaXNDbGFzc1JhdGUtUENBXzIwX3ZfTmV1cmFsTmV0TWlzQ2xhc3NSYXRlKSoxMDAsMilgJQ0KICAgIA0KIyMjIDMuMi40KSBLLU5lYXJlc3QgTmVpZ2hib3Vycw0KICANCmtOTiB3b3JrcyBieSBjb21wdXRpbmcgdGhlIGV1Y2xpZGVhbiBkaXN0YW5jZXMgb2YgdGhlIHRlc3QgZmVhdHVyZXMgdG8gdHJhaW5pbmcgZGF0YSBwb2ludHMsIGtub3duIGFzIOKAnG5laWdoYm91cnPigJ0uIFRoaXMgbW9kZWwgcmVxdWlyZXMgcHJlLXByb2Nlc3NpbmcgYmVjYXVzZSB0aGUgZGlzdGFuY2VzIGZyb20gZWFjaCBkYXRhIHBvaW50IG11c3QgYmUgaW4gdGhlIHNhbWUgc2NhbGUsIHRoZXJlZm9yZSB3ZSBmaXJzdCBub3JtYWxpemVkIHRoZSB0cmFpbmluZyBhbmQgdGVzdGluZyBmZWF0dXJlcy4gVGhpcyBtb2RlbCBoYXMgYW4gaW5wdXQgcGFyYW1ldGVyLCBrLCB3aGljaCByZXByZXNlbnRzIHRoZSBudW1iZXIgb2YgbmVpZ2hib3VycyBjb25zaWRlcmVkLiBCb3RoIHRvby1zbWFsbCBhbmQgdG9vLWxhcmdlIHZhbHVlcyBvZiBrIGNhbiBiZSBkZXRyaW1lbnRhbC4gVG8gdHVuZSB0aGlzIHBhcmFtZXRlciwgd2UgdGVzdGVkIHNldmVyYWwgdmFsdWVzIG9mIGsgaW4gYSBsb29wLCBzZWxlY3RpbmcgdGhlIGsgd2hpY2ggcmVzdWx0ZWQgaW4gdGhlIGxvd2VzdCBtaXNjbGFzc2lmaWNhdGlvbiByYXRlLg0KDQpgYGB7cn0NCm51bUtzID0gMTANCmtfZXJyb3JzID0gcmVwKDAsIG51bUtzKQ0KZm9yIChraSBpbiAxOm51bUtzKSB7DQogICN0dW5lIGsgcGFyYW1ldGVyDQogIGF2Z0Vycm9yc19mb2xkID0gcmVwKDAsIDUpDQogIGZvciAoZm9sZCBpbiAxOjUpIHsNCiAgICAjay1mb2xkIGNyb3NzIHZhbGlkYXRpb24NCiAgICBzZXQuc2VlZChmb2xkKQ0KICAgICNtYWtlIG5vcm1hbGl6ZWQgdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cw0KICAgIHNjYWxlZF9kYXRhID0gZGF0YS5mcmFtZShzY2FsZShsYWJlbGVkX2ZpbmFsWywgMjozNl0pKQ0KICAgIHNjYWxlZF9kYXRhJGtvaV9kaXNwb3NpdGlvbiA9IGlmZWxzZShsYWJlbGVkX2ZpbmFsJGtvaV9kaXNwb3NpdGlvbiA9PSAiQ09ORklSTUVEIiwgMSwgMCkNCiAgICBzdW1tYXJ5KHNjYWxlZF9kYXRhKQ0KICAgIGhlYWQoc2NhbGVkX2RhdGEpDQogICAgbnVtX3NhbXBsZXMgPSBkaW0oc2NhbGVkX2RhdGEpWzFdDQogICAgc2FtcGxpbmcucmF0ZSA9IDAuOA0KICAgIHRyYWluaW5nID0gc2FtcGxlKDE6bnVtX3NhbXBsZXMsIHNhbXBsaW5nLnJhdGUgKiBudW1fc2FtcGxlcywgcmVwbGFjZSA9IEZBTFNFKQ0KICAgIHRyYWluaW5nU2V0Lm5vcm0gPSBzdWJzZXQoc2NhbGVkX2RhdGFbdHJhaW5pbmcsXSkNCiAgICB0ZXN0aW5nID0gc2V0ZGlmZigxOm51bV9zYW1wbGVzLCB0cmFpbmluZykNCiAgICB0ZXN0aW5nU2V0Lm5vcm0gPSBzdWJzZXQoc2NhbGVkX2RhdGFbdGVzdGluZywgXSkNCiAgICBzaXplVGVzdFNldCA9IGRpbSh0ZXN0aW5nU2V0Lm5vcm0pWzFdDQogICAgDQogICAgdHJhaW5pbmdmZWF0dXJlcyA9IHN1YnNldCh0cmFpbmluZ1NldC5ub3JtLCBzZWxlY3QgPSBjKC1rb2lfZGlzcG9zaXRpb24pKQ0KICAgIHRyYWluaW5nbGFiZWxzID0gdHJhaW5pbmdTZXQubm9ybSRrb2lfZGlzcG9zaXRpb24NCiAgICB0ZXN0aW5nZmVhdHVyZXMgPSBzdWJzZXQodGVzdGluZ1NldC5ub3JtLCBzZWxlY3QgPSBjKC1rb2lfZGlzcG9zaXRpb24pKQ0KICAgIHRlc3RpbmdsYWJlbHMgPSB0ZXN0aW5nU2V0Lm5vcm0ka29pX2Rpc3Bvc2l0aW9uDQogICAgDQogICAgI2ZpdCBtb2RlbCBhbmQgcHJlZGljdA0KICAgIHByZWRpY3RlZExhYmVscyA9IGtubih0cmFpbmluZ2ZlYXR1cmVzLCB0ZXN0aW5nZmVhdHVyZXMsIHRyYWluaW5nbGFiZWxzLCBrID0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBraSkNCiAgICAjZGV0ZXJtaW5lIGVycm9yDQogICAgZXJyb3IgPSBzdW0ocHJlZGljdGVkTGFiZWxzICE9IHRlc3RpbmdTZXQubm9ybSRrb2lfZGlzcG9zaXRpb24pDQogICAgDQogICAgbWlzY2xhc3NpZmljYXRpb25fcmF0ZSA9IGVycm9yIC8gc2l6ZVRlc3RTZXQNCiAgICBhdmdFcnJvcnNfZm9sZFtmb2xkXSA9IG1pc2NsYXNzaWZpY2F0aW9uX3JhdGUNCiAgfQ0KICANCiAga19lcnJvcnNba2ldID0gbWVhbihhdmdFcnJvcnNfZm9sZCkNCn0NCnByaW50KG9yZGVyKGtfZXJyb3JzKSkNCiNUaGUgbG93ZXN0IGF2ZXJhZ2UgZXJyb3IgKHRoaXMgcnVuKSBpcyBmcm9tIHRoZSBtb2RlbCB3aXRoIGsgPSA0Lg0KQXZnRXJyb3JfYmVzdF9rbm4gPSBrX2Vycm9yc1tvcmRlcihrX2Vycm9ycylbMV1dDQpgYGANCktOTiBwcm9kdWNlcyBhbiBlcnJvciBvZiBgciByb3VuZChBdmdFcnJvcl9iZXN0X2tubioxMDAsMilgJSwgd2l0aCBhbiBvcHRpbWFsIGstdmFsdWUgb2YgNA0KDQoNCiMjIyAzLjIuNSkgSy1NZWFucyBDbHVzdGVyaW5nDQpDbHVzdGVyaW5nIGlzIGFuIHVuc3VwZXJ2aXNlZCBtb2RlbCB3aGljaCB1c2VzIHJhbmRvbWx5IGdlbmVyYXRlZCBjZW50cm9pZHMgYW5kIGFzc2lnbnMgZXZlcnkgcG9pbnQgdG8gYSBjZW50cm9pZCBiYXNlZCBvbiBldWNsaWRlYW4gZGlzdGFuY2UuIFRoZSBtb2RlbCB0aGVuIGl0ZXJhdGVzIHRvIGZpbmQgdGhlIGNlbnRyb2lkIGxvY2F0aW9ucyB3aGljaCBtaW5pbWl6ZSB0aGUgZGlzdGFuY2VzIHRvIHRoZSBkYXRhIHBvaW50cyBpbiB0aGUgY2x1c3RlcnMuIFNpbmNlIHRoaXMgbW9kZWwgYWxzbyByZXF1aXJlcyBjYWxjdWxhdGlvbiBvZiBldWNsaWRlYW4gZGlzdGFuY2UsIHRoZSBub3JtYWxpemVkIGRhdGEgc2V0IHdhcyBhbHNvIHVzZWQgaGVyZS4gDQoNClNpbmNlIG91ciBkYXRhIHNldCB3YXMgbGFiZWxsZWQsIHRoZSBzdXBlcnZpc2VkIG1vZGVscyB3aWxsIGxpa2VseSB5aWVsZCBhIGJldHRlciBtaXNjbGFzc2lmaWNhdGlvbiByYXRlIHRoYW4gay1NZWFucyBjbHVzdGVyaW5nLiBXZSBpbmNsdWRlIGNsdXN0ZXJpbmcgaW4gY2FzZSB0aGUgYWxnb3JpdGhtIGZvdW5kIHVuZm9yZXNlZW4gcmVsYXRpb25zaGlwcyBpbiB0aGUgdW5sYWJlbGxlZCBkYXRhIGZlYXR1cmVzLg0KDQpgYGB7cn0NCiNzY2FsZSBmdWxsIGRhdGEgc2V0LiANCnNldC5zZWVkKDEyMykNCnNjYWxlZF9kYXRhID0gZGF0YS5mcmFtZShzY2FsZShsYWJlbGVkX2ZpbmFsWywgMjozNl0pKQ0Kc2NhbGVkX2RhdGEka29pX2Rpc3Bvc2l0aW9uID0gaWZlbHNlKGxhYmVsZWRfZmluYWwka29pX2Rpc3Bvc2l0aW9uID09ICJDT05GSVJNRUQiLCAyLCAxKQ0KbnVtX3NhbXBsZXMgPSBkaW0oc2NhbGVkX2RhdGEpWzFdDQpmZWF0dXJlcyA9IHN1YnNldChzY2FsZWRfZGF0YSwgc2VsZWN0ID0gYygta29pX2Rpc3Bvc2l0aW9uKSkNCiNmaXQgdGhlIG1vZGVsDQprY2x1c3RlcmluZyA9IGttZWFucyhmZWF0dXJlcywgY2VudGVycyA9IDIsIG5zdGFydCA9IDI1KQ0KI3Zpc3VhbGl6ZSB0aGUgY2x1c3RlcnMNCmZ2aXpfY2x1c3RlcihrY2x1c3RlcmluZywgZGF0YSA9IGZlYXR1cmVzKQ0KZXJyb3IgPSBzdW0oc2NhbGVkX2RhdGEka29pX2Rpc3Bvc2l0aW9uICE9IGtjbHVzdGVyaW5nJGNsdXN0ZXIpDQptaXNjbGFzc2lmaWNhdGlvbl9yYXRlID0gZXJyb3IgLyBkaW0oc2NhbGVkX2RhdGEpWzFdDQpBdmdFcnJvckNsdXN0ZXJpbmcgPSBtaXNjbGFzc2lmaWNhdGlvbl9yYXRlDQppZiAoQXZnRXJyb3JDbHVzdGVyaW5nID4gLjUpIHsNCiAgQXZnRXJyb3JDbHVzdGVyaW5nID0gMSAtIEF2Z0Vycm9yQ2x1c3RlcmluZyNzaW5jZSBjbHVzdGVyaW5nIGRvZXMgbm90IGtub3cgd2hpY2ggY2x1c3RlciBpcyBDT05GSVJNRUQgYW5kIHdoaWNoIGlzIEZBTFNFIFBPU0lUSVZFLCB0aGV5IGNhbiBiZSBmbGlwcGVkDQp9DQoNCmBgYA0KVW5zdXJwcmlzaW5nbHksIGNsdXN0ZXJpbmcgaGFzIHRoZSBsYXJnZXN0IGVycm9yLCBvZiBgciByb3VuZChBdmdFcnJvckNsdXN0ZXJpbmcqMTAwLDIpYCU7IGdpdmVuIHRoaXMgaXMgYW4gdW5zdXBlcnZpc2VkIG1ldGhvZCBhbmQgb3VyIGRhdGEgaGFzIGxhYmVscy4gDQoNCiMgNCkgU2VsZWN0IEJlc3QgTW9kZWwNCg0KYGBge3J9DQplcnJvcl9vdXRwdXQgPSBkYXRhLmZyYW1lKA0KICAiTW9kZWwiID0gYygNCiAgICAiRGVjaXNpb24gVHJlZSIsDQogICAgIkdMTSIsDQogICAgIlJhbmRvbSBGb3Jlc3QiLA0KICAgICJTVk0iLA0KICAgICJOZXVyYWwgTmV0IiwNCiAgICAiS05OIiwNCiAgICAiQ2x1c3RlcmluZyIsDQogICAgIlBDQSAxMyBWYXIgTk4iLA0KICAgICJQQ0EgMjAgVmFyIE5OIiwNCiAgICAiWEdCb29zdCINCiAgKSwNCiAgIk1pc2NsYXNzaWZpY2F0aW9uIFJhdGUiID0gYygNCiAgICBBdmdFcnJvckRULA0KICAgIEF2Z0Vycm9yR0xNLA0KICAgIEF2Z0Vycm9yUkYsDQogICAgQXZnRXJyb3JTVk0sDQogICAgTmV1cmFsTmV0TWlzQ2xhc3NSYXRlLA0KICAgIEF2Z0Vycm9yX2Jlc3Rfa25uLA0KICAgIEF2Z0Vycm9yQ2x1c3RlcmluZywNCiAgICBQQ0FfMTNfdl9OZXVyYWxOZXRNaXNDbGFzc1JhdGUsDQogICAgUENBXzIwX3ZfTmV1cmFsTmV0TWlzQ2xhc3NSYXRlLA0KICAgIEF2Z0Vycm9yWEdCDQogICkNCikNCnByaW50KGVycm9yX291dHB1dCkNCmBgYA0KWEdCb29zdCBoYXMgdGhlIGxvd2VzdCBtaXNjbGFzc2lmaWNhdGlvbiByYXRlLiBXZSB3aWxsIHJlbWFrZSB0aGlzIG1vZGVsIHVzaW5nIHRoZSBmdWxsIGRhdGFzZXQuDQoNCg0KIzUgTWFrZSBwcmVkaWN0aW9ucw0KDQpSZW1ha2UgdGhlIFhHQm9vc3QgbW9kZWwgdXNpbmcgdGhlIGZ1bGwgZGF0YXNldDoNCmBgYHtyfQ0KICAgeGdEYXRhID0gZGF0YS5tYXRyaXgobGFiZWxlZF9maW5hbCkNCiAgICB4Z0RhdGFbLCAxXSA9IGlmZWxzZSh4Z0RhdGFbLCAxXSA9PSAyLCAxLCAwKQ0KICAgDQogICAgeGdCb29zdE1vZGVsRmluYWwgPSB4Z2Jvb3N0KA0KICAgICAgZGF0YSA9IHhnRGF0YVssIDI6MzZdLA0KICAgICAgbGFiZWwgPSB4Z0RhdGFbLCAxXSwNCiAgICAgIG1heC5kZXB0aCA9IDYsDQogICAgICBldGEgPSAuMjIsDQogICAgICBucm91bmRzID0gMTAwLA0KICAgICAgdmVyYm9zZSA9IDAsDQogICAgICBvYmplY3RpdmUgPSAiYmluYXJ5OmxvZ2lzdGljIiwNCiAgICAgIGV2YWxfbWV0cmljPSJlcnJvciINCiAgICApDQogICAgeGdQcmVkaWN0ID0gZGF0YS5tYXRyaXgoY2FuZGlkYXRlc19maW5hbCkNCiAgICB4Z1ByZWRpY3RbLCAxXSA9IGlmZWxzZSh4Z1ByZWRpY3RbLCAxXSA9PSAyLCAxLCAwKQ0KICAgIA0KICAgDQpgYGANClByZWRpY3QgbGFiZWxzIG9mIGNhbmRpZGF0ZXMgZGF0YXNldCwgYW5kIHdyaXRlIGl0IHRvIGZpbGUNCmBgYHtyfQ0KICNtYWtlIHByZWRpY3Rpb25zDQpCb29zdFByZWRpY3Rpb25zID0gcHJlZGljdCh4Z0Jvb3N0TW9kZWxGaW5hbCwgZGF0YS5tYXRyaXgoY2FuZGlkYXRlc19maW5hbClbLCAyOjM2XSkNCkJvb3N0UHJlZGljdGlvbnNSb3VuZGVkID0gaWZlbHNlKEJvb3N0UHJlZGljdGlvbnMgPiAuNCwgMSwgMCkNCnByZWRpY3RlZExhYmVscyA9IGlmZWxzZShCb29zdFByZWRpY3Rpb25zUm91bmRlZCA9PSAxLCAiRkFMU0UgUE9TSVRJVkUiLCAiQ09ORklSTUVEIikNCmNhbmRpZGF0ZXNfZmluYWwka29pX2Rpc3Bvc2l0aW9uID0gcHJlZGljdGVkTGFiZWxzDQpoZWFkKGNhbmRpZGF0ZXNfZmluYWwpDQp3cml0ZS5jc3YoY2FuZGlkYXRlc19maW5hbCwgImxhYmVsZWRDYW5kaWRhdGVzLmNzdiIpDQpgYGANCg0KIA0KIA==